封面.png

使用 Unity 掌握 UI 开发

Mastering UI Development with Unity

版权所有 © 2024 Packt Publishing

Copyright © 2024 Packt Publishing

保留所有权利。未经出版商事先书面许可,不得以任何形式或任何手段复制、存储于检索系统或传播本书的任何部分,但批判性文章或评论中嵌入的简短引文除外。

All rights reserved. No part of this book may be reproduced, stored in a retrieval system, or transmitted in any form or by any means, without the prior written permission of the publisher, except in the case of brief quotations embedded in critical articles or reviews.

在编写本书的过程中,我们尽了最大努力确保所提供信息的准确性。但是,本书中包含的信息在出售时不提供任何明示或暗示的保证。作者、Packt Publishing 或其经销商和分销商均不对本书直接或间接造成的任何损害承担责任。

Every effort has been made in the preparation of this book to ensure the accuracy of the information presented. However, the information contained in this book is sold without warranty, either express or implied. neither the author, nor Packt Publishing or its dealers and distributors, will be held liable for any damages caused or alleged to have been caused directly or indirectly by this book.

Packt Publishing 已尽力以适当的大写字母形式提供本书中提及的所有公司和产品的商标信息。但是,Packt Publishing 无法保证这些信息的准确性。

Packt Publishing has endeavored to provide trademark information about all of the companies and products mentioned in this book by the appropriate use of capitals. However, Packt Publishing cannot guarantee the accuracy of this information.

集团产品经理Rohit Rajkumar

Group Product Manager: Rohit Rajkumar

出版产品经理Kaustubh Manglurkar

Publishing Product Manager: Kaustubh Manglurkar

图书项目经理Sonam Pandey

Book Project Manager: Sonam Pandey

高级编辑Debolina Acharyya

Senior Editor: Debolina Acharyya

技术编辑Reenish Kulshrestha

Technical Editor: Reenish Kulshrestha

文字编辑Safis 编辑

Copy Editor: Safis Editing

校对Safis 编辑

Proofreader: Safis Editing

索引编制者Rekha Nair

Indexer: Rekha Nair

制作设计师维杰·坎布尔

Production Designer: Vijay Kamble

DevRel 营销协调员:Anamika Singh 和Nivedita Pandey

DevRel Marketing Coordinators: Anamika Singh and Nivedita Pandey

首次发布时间:2018 年 4 月

First published: April 2018

第二版:2024 年 6 月

Second edition: June 2024

生产编号:1080524

Production reference: 1080524

由 Packt Publishing Ltd.出版。

Published by Packt Publishing Ltd.

格罗夫纳大厦

Grosvenor House

11 圣保罗广场

11 St Paul’s Square

伯明翰

Birmingham

英国B3 1RB

B3 1RB, UK

ISBN 978-1-80323-539-4

ISBN 978-1-80323-539-4

www.packtpub.com

www.packtpub.com

感谢我的丈夫凯尔和女儿克莱尔,他们支持我所做的一切。感谢我的宠物,小狗 D. 路飞、娜美·斯旺、京和莫娜·Persona 5,在我写作时,它们会依偎在我身边。

To my husband, Kyle, and daughter, Claire, who are supportive of everything I do. To my pets, Doggy D. Luffy, Nami Swan, Kyo, and Mona Persona 5, who cuddle with me while I write.

– Ashley Godbold 博士

– Dr. Ashley Godbold

贡献者

Contributors

关于作者

About the author

Ashley Godbold 博士是一名程序员、艺术家、作家和教授。她拥有数学理学学士学位、数学理学硕士学位、游戏艺术与设计理学学士学位以及新兴媒体计算机科学博士学位,她的论文研究重点是教育视频游戏设计。她是一家 AA 游戏工作室的工程师,并经营着自己的小型独立工作室。她在大学教授游戏编程和开发、信息技术、计算机科学、数学和数据科学课程。她把闲暇时间用来和丈夫、女儿、狗和三只猫一起看动漫和玩电子游戏。

Dr. Ashley Godbold is a programmer, artist, author, and professor. She holds a Bachelor of Science in mathematics, a master of science in mathematics, a bachelor of science in game art and design, and a doctorate of computer science in emerging media, where her dissertation research focused on educational video game design. She is an engineer for an AA game studio and runs her own small indie studio. She teaches college courses in game programming and development, information technology, computer science, mathematics, and data science. She spends her free time watching anime and playing video games with her husband, daughter, dog, and three cats.

我要感谢 Packt 的优秀员工,他们帮助我完成了第二版,尽管我完全无法遵守最后期限,但他们对我表现出了极大的耐心——尤其是 Debolina!

I want to thank the wonderful people at Packt who helped me finish this second edition and showed an incredible amount of patience for me, despite my absolute inability to stick to a deadline – especially Debolina!

关于审稿人

About the reviewers

Dmitrii Ivashchenko是一位经验丰富的 Unity 游戏开发人员,在移动游戏和后端系统方面拥有超过 11 年的经验。在 MY.GAMES,他领导着一支致力于制作引人入胜的游戏的开发团队,为公司覆盖全球超过 10 亿用户的广泛产品组合做出了贡献。作为国际游戏开发者协会和互动艺术与科学学院的活跃成员,Dmitrii 通过文章和会议演讲分享他深厚的 Unity 知识。他还曾担任国际奖项的评委,并参与开源项目。他的目标是指导和提升游戏开发社区,体现他对创新的热情和对游戏创作卓越的追求。

Dmitrii Ivashchenko is a skilled Unity game developer with over 11 years of experience in mobile gaming and backend systems. At MY.GAMES, he leads a development team dedicated to crafting engaging games, contributing to the company’s extensive portfolio that reaches over 1 billion users globally. An active member of the International Game Developers Association and the Academy of Interactive Arts and Sciences, Dmitrii shares his profound Unity knowledge through articles and conference presentations. He has also served as a judge for international awards and participated in open source projects. He aims to mentor and elevate the game development community, reflecting his passion for innovation and the pursuit of excellence in game creation.

Michael Ross是一位自学成才的 UI/UX 远见者,他通过自己的公司 Neon Raven 将自己的专业知识应用于医疗、汽车、虚拟现实和游戏等行业。Michael 以开创性的 3D CT 成像和汽车工具而闻名,他旨在利用自己丰富的经验广泛分享自己的知识并支持 Packt Publishing 的使命。Neon Raven 是游戏开发和 UI 创新的融合,在 Michael 的领导下蓬勃发展,而 Nuno Duarte 的杰出艺术才华又让 Neon Raven 焕发生机,Nuno Duarte 无限的创造力和对完美的执着为 Neon Raven 的成功留下了不可磨灭的印记。Michael 和 Nuno 共同驾驭不断发展的 UI 开发领域,对创新的热情和对卓越的承诺将他们团结在一起。

Michael Ross, a self-taught UI/UX visionary, has applied his expertise across industries that include medical, automotive, VR, and gaming through his company, Neon Raven. Known for pioneering 3D CT imaging and automotive tools, Michael aims to use his diverse experience to share his knowledge broadly and support Packt Publishing’s mission. Neon Raven, where game development and UI innovation converge, thrives under Michael’s leadership, which is brought to life by the exceptional artistry of Nuno Duarte, whose boundless creativity and dedication to perfection has left an indelible mark on Neon Raven’s success. Together, Michael and Nuno navigate the ever-evolving realm of UI development, united by a passion for innovation and a commitment to excellence.

目录

Table of Contents

前言

Preface

第一部分:设计用户界面

Part 1: Designing User Interfaces

1

1

设计用户界面

Designing User Interfaces

技术要求

Technical requirements

定义 UI 和 GUI

Defining UI and GUI

四种游戏界面类型

The four game interface types

布局 UI 元素

Laying out the UI elements

分辨率和长宽比

Resolution and aspect ratio

更改游戏视图的宽高比和分辨率

Changing the aspect ratio and resolution of the game view

构建单一解决方案

Building for a single resolution

概括

Summary

2

2

设计移动用户界面

Designing Mobile User Interfaces

技术要求

Technical requirements

设置分辨率、宽高比和方向

Setting the resolution, aspect ratio, and orientation

在游戏视图中设置分辨率

Setting the resolution in the Game view

设备模拟器

The Device Simulator

为特定方向建造

Building for a specific orientation

建议按钮尺寸

Recommended button sizes

全屏/部分屏幕点击

Full screen/screen portion taps

拇指区域

The thumb zone

其他移动输入

Other mobile inputs

设备特定资源

Device-specific resources

概括

Summary

3

3

设计 VR、MR 和 AR UI

Designing VR, MR, and AR UI

什么是XR、VR、MR和AR?

What are XR, VR, MR, and AR?

为 VR 设计 UI

Designing UI for VR

视觉 UI 位置和注意事项

Visual UI placement and considerations

可交互 UI 位置和注意事项

Interactable UI placement and considerations

为 MR 设计 UI

Designing UI for MR

为 AR 设计 UI

Designing UI for AR

概括

Summary

进一步阅读

Further reading

4

4

UI 的通用设计和可访问性

Universal Design and Accessibility for UI

什么是通用设计和无障碍设计?

What are universal design and accessible design?

通用设计原则

Universal design principles

公平使用

Equitable use

使用灵活

Flexibility in use

使用简单、直观

Simple and intuitive use

可感知的信息

Perceptible information

容错率

Tolerance of error

低体力投入

Low physical effort

尺寸和空间,方便接近和使用

Size and space for approach and use

无障碍设计

Accessibility design

想象

Vision

听力和言语

Hearing and speech

移动性

Mobility

认知和情感

Cognitive and emotional

其他资源

Additional resources

概括

Summary

5

5

Unity 中的用户界面和输入系统

User Interface and Input Systems in Unity

三个 UI 系统

The three UI systems

Unity UI(或 uGUI)

Unity UI (or uGUI)

图形用户界面

IMGUI

UI 工具包

UI Toolkit

在 UI 系统之间进行选择

Choosing between the UI systems

两种输入系统

The two input systems

输入管理器

The Input Manager

新的输入系统

The new Input System

在输入系统和新输入系统之间进行选择

Choosing between the Input System and the new Input System

概括

Summary

第 2 部分:Unity UI 基础知识

Part 2: Unity UI Basics

6

6

画布、面板和基本布局

Canvases, Panels, and Basic Layouts

技术要求

Technical requirements

用户界面画布

UI Canvas

Rect Transform 组件

Rect Transform component

Canvas 组件

Canvas component

Canvas Scalar 组件

Canvas Scalar component

图形 Raycaster 组件

Graphic Raycaster component

Canvas Renderer 组件

Canvas Renderer component

UI面板

UI Panel

矩形变换

Rect Transform

矩形工具

Rect Tool

Rect Transform 组件

Rect Transform component

锚点和枢轴点

Anchor and Pivot Points

Canvas Group 组件

Canvas Group component

介绍 UI 文本和图像

Introducing UI Text and Image

示例

Examples

布置基本HUD

Laying out a basic HUD

放置 2D 游戏背景图像

Placing a 2D game background image

设置基本弹出菜单

Setting up a basic pop-up menu

概括

Summary

7

7

探索自动布局

Exploring Automatic Layouts

技术要求

Technical requirements

自动布局组的类型

Types of automatic layout groups

水平布局组

Horizontal Layout Group

垂直布局组

Vertical Layout Group

网格布局组

Grid Layout Group

布局元素

Layout Element

忽略布局

Ignore Layout

宽度和高度属性

The Width and Height properties

装配工

Fitters

内容尺寸适配器

Content Size Fitter

宽高比调整器

Aspect Ratio Fitter

示例

Examples

布局 HUD 选择菜单

Laying out a HUD selection menu

布置网格清单

Laying out a grid inventory

概括

Summary

8

8

事件系统和 UI 编程

The Event System and Programming for UI

技术要求

Technical requirements

在代码中访问 UI 元素

Accessing UI elements in code

UnityEngine.UI 命名空间

UnityEngine.UI namespace

UI 变量类型

UI variable types

事件系统

The Event System

事件系统经理

Event System Manager

输入管理器

Input Manager

按钮和按键的输入功能

Input functions for buttons and key presses

获取按钮

GetButton

获取轴

GetAxis

获取密钥

GetKey

获取鼠标按钮

GetMouseButton

输入模块

Input Modules

独立输入模块

Standalone Input Module

基本输入模块/指针输入模块

BaseInputModule/PointerInputModule

多点触控输入

Input for multi-touch

加速度计和陀螺仪输入

Input for accelerometer and gyroscope

事件触发器

Event Trigger

事件类型

Event types

向事件添加动作

Adding an action to the event

事件输入

Event inputs

射线投射器

Raycasters

图形光线投射器

Graphic Raycaster

其他射线投射器

Other Raycasters

示例

Examples

通过按键显示和隐藏弹出菜单

Showing and hiding pop-up menus with keypress

暂停游戏

Pausing the game

拖放库存物品

Dragging and dropping inventory items

使用鼠标和多点触控输入进行平移和缩放

Pan and zoom with mouse and multi-touch input

概括

Summary

第 3 部分:可交互的 Unity UI 组件

Part 3: The Interactable Unity UI Components

9

9

UI 按钮组件

The UI Button Component

技术要求

Technical requirements

用户界面按钮

UI Button

按钮组件

The Button component

过渡

Transitions

导航

Navigation

不可见的按钮区域

Invisible button zones

示例

Examples

通过按钮导航并使用“第一个选定”

Navigating through Buttons and using First Selected

按下按钮加载场景

Loading scenes with Button presses

按钮动画过渡

Button Animation Transitions

概括

Summary

10

10

UI Text 和 TextMeshPro

UI Text and TextMeshPro

技术要求

Technical requirements

UI 文本游戏对象

UI Text GameObject

文本和字符属性

The Text and Character properties

段落属性

The Paragraph properties

颜色和材质属性

The Color and Material properties

Raycast 和 Maskable 属性

The Raycast and Maskable properties

文字-TextMeshPro

Text-TextMeshPro

文本输入属性

Text Input properties

主要设置

Main Settings

额外设置

Extra Settings

TextMeshPro 项目设置

TextMeshPro Project Settings

使用字体

Working with fonts

导入新字体

Importing new fonts

自定义字体

Custom fonts

字体资源

Font assets

探索标记格式

Exploring the markup format

字体样式

Font style

字体颜色

Font color

字体大小

Font size

使用样式表

Using style sheets

翻译文本

Translating text

示例

Examples

创建动画文本

Creating animated text

翻译对话

Translating the dialogue

自定义字体

Custom font

TextMeshPro - 带渐变的扭曲文本

TextMeshPro - Warped Text with Gradient

概括

Summary

11

11

UI 图像和效果

UI Images and Effects

技术要求

Technical requirements

UI 图像组件属性

UI Image component properties

图像类型

Image Type

UI 效果组件

UI effect components

阴影

Shadow

大纲

Outline

位置为 UV1

Position As UV1

示例

Examples

水平和圆形健康/进度计

Horizontal and circular health/progress meters

静音按钮与精灵交换

Mute Buttons with sprite swap

添加按住/长按功能

Adding press-and-hold/long-press functionality

创建浮动的八向虚拟模拟摇杆

Creating a floating eight-directional virtual analog stick

概括

Summary

12

12

使用遮罩、滚动条和滚动视图

Using Masks, Scrollbars, and Scroll Views

技术要求

Technical requirements

使用蒙版

Using masks

Mask 组件

The Mask component

Rect Mask 2D 组件

Rect Mask 2D component

实现 UI 滚动条

Implementing UI Scrollbars

滚动条组件

The Scrollbar component

实现 UI 滚动视图

Implementing UI Scroll View

滚动矩形组件

Scroll Rect component

示例

Examples

从现有菜单创建滚动视图

Making a scroll view from a pre-existing menu

概括

Summary

十三

13

其他可交互的 UI 组件

Other Interactable UI Components

技术要求

Technical requirements

使用 UI 切换

Using UI Toggle

切换组件

Toggle component

切换组组件

Toggle Group component

UI 滑块

UI Slider

滑块组件

Slider component

UI 下拉菜单和下拉列表 – TextMeshPro

UI Dropdown and Dropdown – TextMeshPro

下拉模板

Dropdown Template

下拉列表组件

The Dropdown component

UI 输入字段

UI Input Field

输入字段组件

Input Field component

输入字段 - TextMeshPro

Input Field - TextMeshPro

TextMeshPro - 输入字段组件

TextMeshPro - Input Field component

示例

Examples

创建带有图像的下拉菜单

Creating a dropdown menu with images

概括

Summary

第 4 部分:Unity UI 高级主题

Part 4: Unity UI Advanced Topics

14

14

动画 UI 元素

Animating UI Elements

技术要求

Technical requirements

动画剪辑

Animation Clips

动画活动

Animation Events

动画控制器

Animator Controller

过渡动画的动画师

The Animator of Transition Animations

动画图层

Animator layers

在脚本中设置动画参数

Setting Animation Parameters in scripts

动画师行为

Animator Behaviours

动画弹出窗口淡入淡出

Animating pop-up windows to fade in and out

制作复杂的战利品盒动画

Animating a complex loot box

概括

Summary

15

15

用户界面中的粒子

Particles in the UI

技术要求

Technical requirements

用户界面中的粒子

Particles in the UI

示例

Examples

创建在 UI 中显示的粒子系统

Creating a Particle System that displays in the UI

概括

Summary

16

16

利用世界空间 UI

Utilizing World Space UI

技术要求

Technical requirements

何时使用 World Space UI

When to use World Space UI

适当缩放画布中的文本

Appropriately scaling text in the Canvas

在世界空间中工作时的其他注意事项

Other considerations when working in World Space

示例

Examples

2D 世界空间状态指示器

2D World Space status indicators

3D 悬浮健康条

3D hovering health bars

概括

Summary

17

17

优化 Unity UI

Optimizing Unity UI

优化基础知识

Optimization basics

帧速率

Frame Rate

GPU 和 CPU

GPU and CPU

确定绩效的工具

Tools for determining performance

统计窗口

Statistics window

Unity 分析器

Unity Profiler

Unity 帧调试器

Unity Frame Debugger

Unity UI 优化基本策略

Basic Unity UI Optimization Strategies

使用多个画布和画布层次结构

Using multiple Canvases and Canvas Hierarchies

尽量减少布局组的使用

Minimizing the use of Layout Groups

适当隐藏物体

Hiding objects appropriately

适当地启用和禁用对象池

Appropriately time object pooling enabling and disabling

减少射线投射计算

Reducing Raycast computations

概括

Summary

进一步阅读

Further reading

第五部分:其他 UI 和输入系统

Part 5: Other UI and Input Systems

18

18

UI Toolkit 入门

Getting Started with UI Toolkit

技术要求

Technical requirements

UI Toolkit 概述

Overview of UI Toolkit

安装 UI Toolkit 包

Installing the UI Toolkit package

UI Toolkit 系统的组成部分

Parts of the UI Toolkit system

视觉元素和 UI 层次结构

Visual Elements and UI Hierarchy

使用 UI Builder 创建 UI

Creating UI with the UI Builder

使用 UI 文档组件

Using the UI Document component

使 UI 可与 C# 交互

Making The UI interactable with C#

UIElements 命名空间

The UIElements namespaces

获取对 UI Documents 变量的引用

Getting a reference to UI Documents variables

管理视觉元素事件

Managing Visual Element events

访问视觉元素属性

Accessing Visual Element properties

示例

Examples

使用 UI 工具包制作编辑器虚拟宠物

Using the UI Toolkit to make an Editor virtual pet

资源

Resources

概括

Summary

19

19

使用 IMGUI

Working with IMGUI

技术要求

Technical requirements

IMGUI 概述

IMGUI overview

IMGUI 控件

IMGUI Controls

检查器中的 IMGUI

IMGUI in the Inspector

示例

Examples

使用 IMGUI 显示游戏中的帧速率

Using IMGUI to show framerate in-game

使用 IMGUI 制作导入数据的检查器按钮

Using IMGUI to make an Inspector button that imports data

概括

Summary

20

20

新的输入系统

The New Input System

技术要求

Technical requirements

安装输入系统

Installing the Input System

投票与订阅

Polling vs subscribing

输入系统元素

Input System elements

将操作连接到代码

Connecting Actions to code

创建基本角色控制器动作

Creating basic character controller Actions

使用 PlayerInput 组件创建基本角色控制器

Creating a basic character controller with the PlayerInput Component

通过引用代码中的 Actions 来创建基本的角色控制器

Creating a basic character controller by referencing Actions in your code

概括

Summary

指数

Index

您可能喜欢的其他书籍

Other Books You May Enjoy

前言

Preface

Unity 内置的游戏中可以加入大量内置 UI 元素。本书将深入介绍各种 UI 对象、功能和属性,并提供逐步实现的示例,帮助您掌握 Unity 的 UI 系统。

There are a multitude of built-in UI elements that can be incorporated into a game built in Unity. This book will help you master Unity’s UI system by describing, in-depth, the various UI objects, functionalities, and properties and providing step-by-step examples of their implementation.

本书适合哪些人阅读

Who this book is for

本书面向使用过 Unity 并希望提高对 Unity 中提供的 UI 系统了解的游戏开发者。对于希望深入了解特定 UI 元素以及希望逐步了解如何实现多种游戏类型中出现的 UI 项目的个人,本书也会有所帮助。需要对 Unity 和 C# 编程有基本的了解

This book is intended for game developers who have worked with Unity and are looking to improve their knowledge of the UI systems provided within Unity. Individuals looking for in-depth explanations of specific UI elements, as well as individuals looking for step-by-step directions explaining how to implement UI items that appear in multiple game genres, will also find this book helpful. A basic understanding of Unity and C# programming is needed.

本书涵盖的内容

What this book covers

第 1 章设计用户界面”涵盖了与设计用户界面相关的基本信息。它定义了图形用户界面和用户界面之间的区别。它讨论了四种不同类型的游戏界面、如何根据设计原则创建美观的 UI 以及界面隐喻的概念。此外,还详细解释了如何设置 Unity 项目的纵横比和分辨率

Chapter 1, Designing User Interfaces, covers basic information related to designing user interfaces. The distinction between a graphic user interface and user interface is defined. It discusses the four different types of game interfaces, how to create an aesthetically pleasing UI based on principles of design, and the concept of interface metaphors. Additionally, a detailed explanation of setting the aspect ratio and resolution of a Unity project is discussed.

第 2 章设计移动用户界面”涵盖了 UI 设计师在开发移动应用时必须考虑的美学和机械方面的因素。此外,它还讨论了可供开发人员使用的资源,以帮助他们设计移动用户界面。

Chapter 2, Designing Mobile User Interfaces, covers considerations that a UI designer must take into account when developing for mobile, both aesthetically and mechanically. Additionally, it discusses the resources available to developers to help them design mobile user interfaces.

第 3 章设计 VR、MR 和 AR UI”涵盖了为 VR、MR 和 AR 应用程序设计用户界面的基本概念。它研究了这些应用程序中的交互与其他应用程序的不同之处,并讨论了设计它们的最佳实践

Chapter 3, Designing VR, MR, and AR UI, covers the basic concepts of designing user interfaces for VR, MR, and AR applications. It looks at how interactions in these applications differ from other applications and discusses the best practices for designing them.

第 4 章UI 的通用设计和可访问性,涵盖了与设计用户界面相关的基本概念,以便让尽可能多的人使用它们。本章将探讨通用设计和可访问性设计的主题,同时讨论 UI 设计师可以采取哪些步骤来确保他们的用户界面尽可能无障碍

Chapter 4, Universal Design and Accessibility for UI, covers basic concepts related to designing user interfaces so that they can be used by the greatest number of people. This chapter will explore the topic of universal design and designing for accessibility while discussing steps that a UI designer can take to make sure their user interfaces are as barrier-free as possible.

第 5 章Unity中的用户界面和输入系统回顾了 Unity 提供的各种 UI 系统。Unity 提供了三个用于设计用户界面的系统和两个用于控制输入的系统。本章探讨了各种系统,比较了它们的优点,并讨论了何时使用其中哪一个

Chapter 5, User Interface and Input Systems in Unity, reviews the various systems provided by Unity to work with UI. Unity provides three systems for designing user interfaces and two systems for controlling the inputs. This chapter explores the various systems, compares their benefits, and discusses when to use which one of them.

第 6 章“画布、面板和基本布局”探讨了如何在画布中适当布局 UI 元素来开发用户界面。本章将使用面板,并介绍文本和图像。本章中包含的示例将向您展示如何布局基本平视显示器、创建永久背景图像以及开发基本弹出菜单。

Chapter 6, Canvases, Panels, and Basic Layouts, explores the development of a user interface by appropriately laying out UI elements within a Canvas. Panels are used, and an introduction to Text and Images is also provided. The examples included in this chapter show you how to lay out a basic heads up display, create a permanent background image, and develop a basic pop-up menu.

第 7 章探索自动布局讨论了如何实现各种自动布局组件以简化 UI 构建过程。本章中包含的示例利用自动布局功能在 HUD 中创建选择菜单和网格库存。

Chapter 7, Exploring Automatic Layouts, discusses how to implement the various automatic layout components to streamline the UI building process. The examples included within this chapter utilize the automatic layout functionality to create a selection menu in the HUD and a gridded inventory.

第 8 章事件系统和 UI 编程,介绍了事件系统及其与 UI 的关系。讨论了如何向 UI 元素添加事件触发器。它介绍了为 UI 系统编程所需的关键字、如何通过代码访问 UI 组件以及如何编写可通过事件触发器访问的函数

Chapter 8, The Event System and Programming for UI, covers the event system and how it pertains to the UI. How to add Event Triggers to UI elements is discussed. It covers the keywords necessary to program for the UI system, how to access UI components via code, and how to write functions that can be accessed via Event Triggers.

第 9 章UI按钮组件探讨了按钮的各种属性。本章中的示例介绍了如何设置按钮的键盘和控制器导航、如何在按下按钮时加载场景、如何创建动画按钮过渡以及如何使按钮交换其图像。

Chapter 9, The UI Button Component, explores the various properties of buttons. The examples in this chapter walk through how to set up keyboard and controller navigation of buttons, how to load scenes when buttons are pressed, how to create animated button transitions, and how to make buttons swap their images.

第 10 章UI文本和 Text-TextMeshPro更详细地讨论了文本的属性,并演示了如何通过代码影响其属性。本章末尾的示例展示了如何创建一个对话框,其中的文本会像输入一样动画,如何创建自定义字体,以及如何创建使用渐变换行的文本。

Chapter 10, UI Text and Text-TextMeshPro, discusses the properties of text more thoroughly and demonstrates how to affect their properties via code. The examples at the end of the chapter show how to create a dialog box with text that animates as if it were being typed in, how to create a custom font, and how to create text that wraps with a gradient.

第 11 章UI图像和效果展示了使用和操作 UI 图像的更多方法。此外,它还演示了如何将各种效果应用于UI 元素。

Chapter 11, UI Images and Effects, shows more ways in which UI images can be used and manipulated. Additionally, it demonstrates how to apply various effects to UI elements.

第 12 章使用蒙版、滚动条和滚动视图”介绍如何创建带有蒙版的可滚动窗口,以便您的 UI 可以容纳比立即可见的更多的项目。

Chapter 12, Using Masks, Scrollbars, and Scroll Views, covers how to create scrollable windows with masks so that your UI can hold more items than are immediately in view.

第 13 章其他可交互 UI 组件本章末尾介绍了如何使用各种输入以及如何创建带图像的下拉菜单的示例

Chapter 13, Other Interactable UI Components, covers a myriad of other UI inputs. Examples of how to use the various inputs and how to create a dropdown menu with images are covered at the end of the chapter.

第 14 章动画 UI 元素”主要介绍动画 UI。本章中的示例展示了如何为菜单添加动画效果以淡入淡出,以及如何使用 Unity状态机创建宝箱动画。

Chapter 14, Animating UI Elements, is all about animating the UI. The examples in this chapter show how to animate menus to fade in and out and how to create a treasure box animation using the Unity State Machine.

第 15 章UI 中的粒子”扩展了上一章的动画示例,并提供了使用粒子效果美化 UI 的更多方法

Chapter 15, Particles in the UI, expands upon the animation example of the previous chapter and provides further ways in which you can zhuzh up your UI using particle effects.

第 16 章利用世界空间 UI”展示了如何创建存在于游戏场景中的 UI 元素,而不是存在于所有游戏内物品前面的“屏幕”中。示例涵盖了如何为 2D 场景创建交互式 UI 以及为3D 场景创建交互式悬停健康栏。

Chapter 16, Utilizing World Space UI, showcases how to create UI elements that exist within the game scene as opposed to on the “screen” in front of all in-game items. The examples cover how to create an interactive UI for a 2D scene and interactive, hover health bars for a 3D scene.

第 17 章优化Unity UI介绍了创建优化用户界面的基本概念。它定义了关键术语,概述了 Unity 中包含的工具(可帮助您确定游戏的性能),并介绍了使用 Unity UI 系统的各种优化策略

Chapter 17, Optimizing Unity UI, covers basic concepts of creating optimized user interfaces. It defines key terms, provides an overview of tools included within Unity that can help you determine how performant your game is, and covers various optimization strategies for working with Unity’s UI system.

第 18 章UI Toolkit 入门,介绍了新的 Unity UI Toolkit,并解释了如何使用它来创建基本布局。它介绍了使用这个不同 UI 系统的关键概念,同时还演示了如何使用它来创建编辑器和运行时 UI。

Chapter 18, Getting Started with UI Toolkit, covers the new Unity UI Toolkit and explains how to use it to create basic layouts. It covers the key concepts of using this different UI system, while also demonstrating how to use it to create both Editor and runtime UI.

第 19 章使用IMGUI讨论了如何使用 IMGUI 系统构建用户界面。在探索 IMGUI 的基本概念之后,本章介绍了如何使用该系统创建在编辑器中和运行时出现的开发人员工具。

Chapter 19, Working with IMGUI, discusses how to use the IMGUI system to build user interfaces. After exploring the basic concepts of IMGUI, the chapter covers how to use the system to create developer tools that appear in the Editor and at runtime.

第 20 章新输入系统”介绍了如何使用新输入系统轻松进行输入设置。它涵盖了发布者-订阅者架构模式,同时介绍了输入系统的基本概念和原理。此外,它还介绍了如何将使用输入管理器的项目更新为使用新输入系统的项目,以及如何通过两种不同的方式将代码连接到输入系统。

Chapter 20, The New Input System, provides an introduction to using the new Input System for easy input setup. It covers the publisher-subscriber architectural pattern while introducing the basic concepts and principles of the Input System. Additionally, it covers how to update a project that uses the Input Manager to one that uses the new Input System, as well as how to connect your code to the Input System in two different ways.

为了充分利用这本书

To get the most out of this book

本书假设您对 Unity 编辑器的导航和使用有很好的理解。此外,本书还假设您对使用 C# 进行 Unity 编程有基本的了解。

This book assumes you have a good understanding of navigating within and working with the Unity Editor. Additionally, it assumes you have a basic understanding of programming in C# for Unity.

本书涵盖的软件/硬件

Software/hardware covered in the book

操作系统要求

Operating system requirements

Unity 2020或更高版本

Unity 2020 or higher

Windows、macOS或 Linux

Windows, macOS, or Linux

除了安装 Unity 之外,您还需要一个代码编辑器 (IDE)。虽然本书没有指定首选 IDE,但支持的 IDE 示例包括 Visual Studio 和JetBrains Rider。

In addition to having Unity installed, you will also need a code editor (IDE). While no preference is established in this book, examples of supported IDEs are Visual Studio and JetBrains Rider.

如果您使用的是本书的数字版,我们建议您自己输入代码或从本书的 GitHub 存储库访问代码(下一节将提供链接)。这样做将帮助您避免与复制和粘贴代码相关的任何潜在错误。

If you are using the digital version of this book, we advise you to type the code yourself or access the code from the book’s GitHub repository (a link is available in the next section). Doing so will help you avoid any potential errors related to the copying and pasting of code.

下载示例代码文件

Download the example code files

您可以从 GitHub 下载本书的示例代码文件,网址为https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition。如果代码有更新,它将在GitHub 存储库中更新。

You can download the example code files for this book from GitHub at https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition. If there’s an update to the code, it will be updated in the GitHub repository.

我们丰富的书籍和视频目录中还有其他代码包,可在https://github.com/PacktPublishing/上找到。快去看看吧!

We also have other code bundles from our rich catalog of books and videos available at https://github.com/PacktPublishing/. Check them out!

使用的惯例

Conventions used

本书使用了许多文本约定

There are a number of text conventions used throughout this book.

文本中的代码:表示文本中的代码字、数据库表名称、文件夹名称、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 句柄。以下是示例:“目前, AnimationComplete触发器有点问题。”

Code in text: Indicates code words in text, database table names, folder names, filenames, file extensions, pathnames, dummy URLs, user input, and Twitter handles. Here is an example: “Currently, there is a bit of a problem with the AnimationComplete trigger.”

代码块设置如下:

A block of code is set as follows:

[System.Serializable]public class Translation { public string languageKey; public string TranslationString; public Font font; public FontStyle fontStyle;}
[System.Serializable]public class Translation {    public string languageKey;    public string translatedString;    public Font font;    public FontStyle fontStyle;}

当我们想让你注意代码块的某个特定部分时,相关的行或项目会以粗体显示:

When we wish to draw your attention to a particular part of a code block, the relevant lines or items are set in bold:

Vector2[] newPositions = new Vector2[]{ Input.GetTouch(0).position,  Input.GetTouch(1).position};
Vector2[] newPositions = new Vector2[]{Input.GetTouch(0).position, Input.GetTouch(1).position};

粗体:表示新术语、重要单词或您在屏幕上看到的单词。例如,菜单或对话框中的单词以粗体显示。以下是示例:“当为可视性属性选择永久时,如果允许相应的移动,则相应的滚动条将保持可见,即使不需要它。”

Bold: Indicates a new term, an important word, or words that you see on screen. For instance, words in menus or dialog boxes appear in bold. Here is an example: “When Permanent is selected for the Visibility property, the respective scrollbar will remain visible, even if it is not needed, if its corresponding movement is allowed.”

提示或重要说明

Tips or important notes

呈现如下状态。

Appear like this.

联系我们

Get in touch

我们随时欢迎读者的反馈

Feedback from our readers is always welcome.

一般反馈:如果您对本书的任何方面有疑问,请发送电子邮件至customercare@packtpub.com ,并在邮件主题中注明书名。

General feedback: If you have questions about any aspect of this book, email us at customercare@packtpub.com and mention the book title in the subject of your message.

勘误表:尽管我们已尽一切努力确保内容的准确性,但错误还是难免。如果您发现本书中有错误,请向我们报告,我们将不胜感激。请访问www.packtpub.com/support/errata并填写表格。

Errata: Although we have taken every care to ensure the accuracy of our content, mistakes do happen. If you have found a mistake in this book, we would be grateful if you would report this to us. Please visit www.packtpub.com/support/errata and fill in the form.

盗版:如果您在互联网上发现任何形式的我们作品的非法复制品,我们将非常感激您向我们提供位置地址或网站名称。请通过copyright@packt.com与我们联系,并提供材料链接。

Piracy: If you come across any illegal copies of our works in any form on the internet, we would be grateful if you would provide us with the location address or website name. Please contact us at copyright@packt.com with a link to the material.

如果您有兴趣成为一名作家:如果您对某个主题有专业知识,并且有兴趣撰写或参与撰写书籍,请访问authors.packtpub.com

If you are interested in becoming an author: If there is a topic that you have expertise in and you are interested in either writing or contributing to a book, please visit authors.packtpub.com.

分享你的想法

Share Your Thoughts

阅读完《使用 Unity 掌握 UI 开发》后,我们很想听听您的想法!请单击此处直接进入本书的 Amazon 评论页面并分享您的反馈。

Once you’ve read Mastering UI Development with Unity, we’d love to hear your thoughts! Please click here to go straight to the Amazon review page for this book and share your feedback.

您的评论对我们和技术社区都很重要,并将帮助我们确保提供优质的内容。

Your review is important to us and the tech community and will help us make sure we’re delivering excellent quality content.

下载本书的免费 PDF 副本

Download a free PDF copy of this book

感谢您购买本书!

Thanks for purchasing this book!

您是否喜欢在旅途中阅读但又无法随身携带纸质书籍?

Do you like to read on the go but are unable to carry your print books everywhere?

您购买的电子书是否与您选择的设备不兼容?

Is your e-book purchase not compatible with the device of your choice?

别担心!现在购买每本 Packt 书籍,您都可以免费获得该书的无 DRM 的 PDF 版本

Don’t worry!, Now with every Packt book, you get a DRM-free PDF version of that book at no cost.

随时随地、使用任何设备进行阅读。搜索、复制您喜爱的技术书籍中的代码,并将其直接粘贴到您的应用程序中。

Read anywhere, any place, on any device. Search, copy, and paste code from your favorite technical books directly into your application.

福利不止于此,您还可以每天在收件箱中独家获得折扣、新闻通讯和精彩的免费内容

The perks don’t stop there, you can get exclusive access to discounts, newsletters, and great free content in your inbox daily

按照以下简单步骤即可获得好处:

Follow these simple steps to get the benefits:

  1. 扫描二维码或访问以下链接:
  2. Scan the QR code or visit the following link:

https://packt.link/free-ebook/9781803235394

https://packt.link/free-ebook/9781803235394

  1. 提交您的购买证明。
  2. Submit your proof of purchase.
  3. 就这样!我们会将免费 PDF 和其他福利直接发送到您的电子邮箱。
  4. That’s it! We’ll send your free PDF and other benefits to your email directly.

第一部分:设计用户界面

Part 1: Designing User Interfaces

在本部分中,您将大致了解在为各种平台设计用户界面时需要考虑的设计原则。本部分探讨了如何为手机游戏和 XR 体验的特殊情况设计 UI。此外,本部分还介绍了如何在考虑通用设计和可访问性的情况下为所有平台设计 UI。最后,本部分比较并讨论了 Unity 中促进 UI 开发的各种系统。

In this part, you will get a general overview of design principles to consider while designing user interfaces for various platforms. How to design UI for the special cases of mobile games and XR experiences is explored. Additionally, how to design UI for all platforms with consideration for universal design and accessibility is covered. Lastly, the various systems within Unity that facilitate the development of UI are compared and discussed.

本部分包含以下章节:

This part has the following chapters:

1

1

设计用户界面

Designing User Interfaces

在使用 UI 时,了解一些设计基础知识非常重要。本章将介绍 UI 设计的基础知识和一些关键概念,帮助您朝着正确的方向前进。

When working with UI, it is important to understand a few design basics. This chapter will cover the foundation of designing UI and a few key concepts to start you off in the right direction.

在本章中,我们将讨论以下主题:

In this chapter, we will discuss the following topics:

  • 定义 UI和 GUI
  • Defining UI and GUI
  • 描述四种类型的接口
  • Describing the four types of interfaces
  • 布局用户界面
  • Laying out your user interfaces
  • 辨别并设置分辨率和宽高比
  • Discerning and setting resolution and aspect ratio

本书不是关于 UI 设计艺术的。它是一本讨论 UI 功能实现的技术文本。但是,我确实想讨论一些 UI 设计的基本设计原则。我不指望你读完本章后就能成为一名出色的 UI 设计师。不过,我确实希望你从本章中获得一些关于布局和设计原则的基本了解,这样你的艺术家朋友就不会太取笑你了。

This book is not about the art of designing UI. It is a technical text that discusses the implementation of UI functionality. However, I do want to discuss some basic design principles of UI design. I don’t expect you to be an amazing UI designer after reading this chapter. I do hope that you get some basic understanding of layout and design principles from this chapter, though, so that maybe your artist friends won’t make too much fun of you.

技术要求

Technical requirements

对于本章,您将需要以下内容:

For this chapter, you will need the following:

Unity 2020.3.26f1或更高版本

Unity 2020.3.26f1 or later

定义 UI 和 GUI

Defining UI and GUI

那么,到底UI 和 GUI 分别代表什么?区别?UI代表用户界面GUI(发音为“gooey”)代表图形用户界面界面意味着与...交互,因此 UI 是让玩家与游戏交互的一组设备。鼠标、键盘、游戏控制器、触摸屏等都是 UI 的一部分。GUI 是图形表示的 UI 的子集。因此,屏幕按钮、下拉菜单和图标都是游戏 GUI 的一部分。由于 GUI 是 UI 的一个子集,许多人(包括我自己)倾向于将 GUI 称为 UI。Unity 还将他们为其提供模板的所有 GUI 项目称为UI。

So, what exactly do UI and GUI stand for, and what’s the difference? UI stands for user interface and GUI (pronounced “gooey”) stands for graphical user interface. To interface means to interact with, so the UI is the set of devices that let a player interact with a game. The mouse, keyboard, game controller, touch screen, and so on are all part of the UI. The GUI is the subset of the UI represented by graphics. So, onscreen buttons, dropdown menus, and icons are all part of a game’s GUI. As the GUI is a subset of the UI, many people (myself included) tend to just refer to the GUI as the UI. Unity also refers to all the GUI items they provide templates for as the UI.

本书将主要关注 GUI 设计,但它将讨论UI 控件的一些非图形方面,例如通过鼠标、屏幕点击、键盘或控制器访问数据。本章将特别介绍不同界面类型的一些基本设计注意事项。

This book will focus primarily on GUI design, but it will discuss some non-graphical aspects of UI controls, such as accessing data from the mouse, screen tap, keyboard, or controller. This chapter specifically will look at some basic design considerations for different interface types.

四种游戏界面类型

The four game interface types

当你说“游戏 UI”时,大多数人们想到了出现在前方的平视显示器HUD )所有游戏内物品。然而,游戏界面实际上有四种不同的类型:非叙事界面、叙事界面、元界面和空间界面。

When you say “game UI,” most people think of the heads-up display (HUD) that appears in front of all the in-game items. However, there are actually four different types of game interfaces: non-diegetic, diegetic, meta, and spatial.

Fagerholt 和 Lorentzon 在 2009 年的论文《超越 HUD:提高 FPS 游戏中玩家沉浸感的用户界面:理学硕士论文》中首次描述了这四种不同的界面类型。从那时起,该术语就被广泛应用于整个 UI 游戏设计领域。您可以http://publications.lib.chalmers.se/records/fulltext/111921.pdf找到原始出版物。

Fagerholt and Lorentzon first described these four different interface types in the 2009 paper Beyond the HUD: User Interfaces for Increased Player Immersion in FPS Games: Master of Science Thesis. Since then, the terminology has been widely used throughout the field of UI game design. You can find the original publication at http://publications.lib.chalmers.se/records/fulltext/111921.pdf.

四者之间的区别是通过以下两个维度的交叉来确定的:

The distinction between the four is determined by a cross of the following two dimensions:

  • 叙事:它是故事的一部分吗?
  • Diegesis: Is it part of the story?
  • 空间性:它存在于游戏环境中吗?
  • Spatiality: Is it in the game’s environment?

下图演示了两个问题之间的交叉关系以及它们如何定义四种类型的接口:

The following diagram demonstrates the cross relationship between the two questions and how they define the four types of interfaces:

图 1.1:四种类型的接口

图 1.1:四种类型的接口

Figure 1.1: Four types of interfaces

游戏的HUD掉落归入非叙事类别。这些信息仅供玩家查看,游戏中的角色并不知道它的存在。它存在于游戏视图的第四面墙上,似乎出现在屏幕上,位于一切事物的前面。示例这种类型的 UI 是无穷无尽的,因为几乎每个游戏都有一些非叙事性的UI 元素。

A game’s HUD falls into the non-diegetic category. This information exists purely for the player to view and the characters within the game are not aware of its presence. It exists on the fourth wall of the game view and appears to be on the screen in front of everything. The examples of this type of UI are endless, as nearly every game has some non-diegetic UI elements.

或者说,叙事界面存在于游戏世界中,游戏内的角色也知道它的存在。常见的例子包括角色查看库存或地图。最广为人知的叙事 UI 示例是《死亡空间》中的库存和生命值显示。库存显示在可玩角色面前弹出的全息显示窗口中,当您选择他的武器时,他可以与窗口交互。他背后的计量表也显示他的生命值。《鬼屋魔影》(2008)的库存也以叙事方式显示。虽然有些 UI 元素只有玩家可以看到,但主角会在夹克口袋中查看库存并与物品交互。《神秘海域:失落的遗产》《孤岛惊魂 2》都使用角色在场景中实际持有并与之交互的地图。《辐射 3》《辐射 4》使用叙事界面在角色的 Pip-Boy 上显示库存和地图,Pip-Boy 永久附在角色的手臂上。当角色乘坐车辆或套装时,游戏中也会使用这种类型的显示,其中盾牌、窗户或驾驶舱上会出现各种显示。

Alternatively, a diegetic interface is one that exists within the game world and the characters within the game are aware of its presence. Common examples of this include characters looking at inventory or maps. The most widely referred-to example of diegetic UI is the inventory and health display within Deadspace. The inventory displays on a holographic display window that pops up in front of the playable character, and he interacts with it as you select his weaponry. His health is also indicated by a meter on his back. The inventory of Alone in the Dark (2008) is displayed in a diegetic way as well. While there are some UI elements that only the player can see, the main character views inventory within their jacket pockets and interacts with the items. Uncharted Lost Legacy and Far Cry 2 both use maps that the characters physically hold in the scene and interact with. Fallout 3 and Fallout 4 use a diegetic interface to display the inventory and map on the character’s Pip-Boy, which is permanently attached to their arm. Games also use this type of display when characters are in a vehicle or suit, where various displays appear on the shield, window, or cockpit.

接口游戏中的角色可以感知到的界面,但它们并未在场景中实际显示。常见的例子是赛车游戏的速度显示。《极限竞速 7》实际上结合使用了元显示和叙事显示来显示速度表。元速度指示器始终显示在屏幕的右下角以供玩家查看。由于角色始终知道自己的驾驶速度,所以他们会注意到这个速度指示器,因此它就是一个元界面。汽车仪表板上还有一个叙事速度表,在第一人称视角下玩游戏时会显示出来。这种显示类型的另一种常见用途是出现在屏幕上但暗示可玩角色正在与之交互的手机。《女神异闻录 5》《凯瑟琳》《侠盗猎车手 5》都使用这种界面类型进行手机交互。

Meta interfaces are interfaces that the characters in the game are aware of, but they are not physically displayed within the scene. Common examples of this are speed displays for racing games. Forza 7 actually uses a combination of meta and diegetic displays for the speedometer. A meta speed indicator is persistently on the lower-right corner of the screen for the player to see. Since the character is constantly aware of how fast they are driving, they would be aware of this speed indicator, therefore making it a meta interface. There is also a diegetic speedometer in the car’s dash that is displayed when playing in first-person view. Another common usage of this type of display is a cell phone that appears on the screen but is implied the playable character is interacting with. Persona 5, Catherine, and Grand Theft Auto 5 all use this interface type for cell phone interactions.

最后一种类型的界面,空间,存在于场景中存在但游戏内的角色却不知道的界面。场景中存在但角色不知道的界面非常常见。这通常用于让玩家知道场景中可交互物品的位置、游戏内角色在做什么或有关场景中角色和物品的信息。例如,在《塞尔达传说:荒野之息》中,敌人头上会出现箭头,指示林克将攻击谁。林克实际上并不知道这些箭头图标;它们存在是为了让玩家知道他正在关注谁。异度之刃 2使用空间界面来指示玩家可以在哪里挖掘,方法是在可挖掘区域上方显示铲子图标

The last type of interface, spatial, exists in the scene, but the characters within the game are not aware of it. Interfaces that exist in the scene but that the characters are not aware of are incredibly common. This is commonly used to let the player know where in the scene interactable items are, what the in-game character is doing, or information about characters and items in the scene. For example, in Legend of Zelda: Breath of the Wild, arrows appear over the heads of enemies, indicating who Link will attack. Link is not actually aware of these arrow icons; they are there for the player to know who he is focusing on. Xenoblade Chronicles 2 uses a spatial interface to indicate where the player can dig by displaying a shovel icon over the diggable areas.

布局 UI 元素

Laying out the UI elements

在设计游戏的 UI 布局时,我强烈建议您查看同类型的其他游戏,并看看他们是如何实现 UI 的。玩游戏,看看你是否感觉良好。

When laying out the UI for your game, I strongly recommend checking other games of the same genre and seeing how they implemented their UI. Play the game and see whether it feels good to you.

如果你不确定如何布局游戏的 UI,我建议将游戏屏幕分成一个带凹槽的网格,如下图所示,并将项目放置在非凹槽区域内:

If you are unsure of how to lay out your game’s UI, I recommend dividing the game’s screen into a guttered grid, like the one shown in the following diagram, and placing items within the non-guttered areas:

图 1.2:带沟槽的网格

图 1.2:带沟槽的网格

Figure 1.2: A guttered grid

您可以根据需要使用任意数量的网格,但参考网格布置项目将有助于确保 UI 以平衡的方式排列。

You can use as many grids as you want, but laying out the items with reference to the grid will help ensure that the UI is arranged in a balanced way.

在大多数情况下,HUD 项目应保留在网格的外边缘。任何显示在中心网格中的 UI 都会限制玩家的视野。因此,此区域适合用于暂停游戏的弹出窗口。

In most cases, the HUD items should remain at the outer edges of the grid. Any UI that displays in the center grids will restrict the player view. So, this area is good for pop-up windows that pause the gameplay.

该设备在确定布局时,游戏将在哪个设备上进行非常重要。如果你的游戏是为移动设备设计的,并且有很多按钮供玩家交互,那么这些按钮通常最适合放在屏幕的底部或侧面。这是因为玩家握持手机的方式,屏幕的顶部中央部分是他们用拇指最难触及的区域。此外,伸手触及这个区域会导致他们用手挡住大部分游戏视图。我们将在第 2 章中更详细地讨论为移动设备设计 UI 。

The device your game will be played on is important when determining the layout. If your game is designed for a mobile device and has a lot of buttons the player will interact with, the buttons are generally best suited for the bottom or side portions of the screen. This is due to the way players hold their phones and the top-center part of the screen is the most difficult area to reach with their thumb. Additionally, reaching for this area will cause them to block the majority of the game view with their hand. We will discuss designing UI for mobile more thoroughly in Chapter 2.

您会注意到,玩电脑游戏时,它们的 UI 往往比手机和主机游戏小得多且更杂乱。这是由于可视性和交互性。用鼠标点击小物体比用手指点击或用方向键选择要容易得多。此外,屏幕分辨率要大得多,这允许UI占用更多空间。

You’ll note that when you play computer games, they tend to have much smaller and more cluttered UI than mobile and console games. This is due to visibility and interaction. Clicking on small objects with a mouse is significantly easier than tapping them with a finger or selecting them with the D-pad. Also, the screen resolution is much bigger, which allows for more space to be taken up by the UI.

在尝试确定 UI 项目的大小和相对位置时,可以参考菲茨定律。菲茨定律可以根据 UI 项目的大小和与用户起始位置的距离,用数学方法计算出用户导航到 UI 项目需要多长时间。我不会在这里讨论数学问题(尽管我内心的数学老师非常想这样做),但可以从菲茨定律中得到以下教训:

When trying to determine the size and relative location of UI items, you can reference Fitts’ Law. Fitts’ Law can mathematically calculate how long it will take a user to navigate to a UI item based on its size and distance away from the user’s starting position. I won’t go over the math here (despite the math teacher in me desperately wanting to), but the lessons that can be garnered from Fitts’ Law are as follows:

  • 不要让可交互的 UI 变得又小又
  • Don’t make interactable UI small and far apart
  • 使最重要的可交互项目最大且彼此靠近
  • Make the most important interactable items the largest and near each other

接下来,我们来讨论分辨率和纵横比。

Next, we’ll look at resolution and aspect ratio.

分辨率和长宽比

Resolution and aspect ratio

游戏的分辨率是游戏屏幕的像素尺寸。例如,游戏可以在 1,024x768 分辨率下运行。这意味着游戏宽度为 1,024 像素和 768 像素高。游戏的宽高比是宽度和高度的比率(表示为宽度:高度)。此宽高比通过将分辨率宽度除以分辨率高度然后简化分数来确定。因此,例如,如果您的游戏的分辨率为 1024x768,则宽高比将如下所示:

A game’s resolution is the pixel dimension of the screen on which it plays. For example, a game could run at 1,024x768. This means that the game is 1,024 pixels wide and 768 pixels tall. The aspect ratio of a game is the ratio of the width and height (expressed as width:height). This aspect ratio is determined by dividing the resolution width by the resolution height and then simplifying the fraction. So, for example, if your game has a resolution of 1024x768, the aspect ratio would be as follows:

1024像素/768像素=4/3

1024px/768px=4/3

这里,分数 4/3 表示纵横比 4:3。

Here, the fraction 4/3 is the aspect ratio 4:3.

下表列出了常见的宽高比和相关分辨率:

The following table provides a list of common aspect ratios and related resolutions:

图 1.3:常见的宽高比和分辨率

图 1.3:常见的宽高比和分辨率

Figure 1.3: Common aspect ratios and resolutions

在设计 UI 时,分辨率和宽高比对于 UI 的外观起着重要作用。了解目标设备的分辨率和宽高比是设计 UI 的重要第一步,原因有二:

When designing your UI, the resolution and aspect ratio will play an important role in how your UI will look. Knowing the resolution and aspect ratio of your target device will be an important first step in designing your UI for two reasons:

  • 它将决定你的 UI布局
  • It will determine the layout of your UI
  • 在 Unity 中构建 UI 的方式将取决于你计划支持的分辨率和宽高比数量
  • The way you build the UI within Unity will be determined by how many resolutions and aspect ratios you plan to support

如果您构建单一分辨率/宽高比,则 UI 构建起来会容易得多,因为您不必确保所有元素在多个宽高比下保持其相对位置。但是,如果您构建的游戏将在多个分辨率/宽高比下运行(例如,移动项目或在窗口内缩放的 Web 游戏),您希望您的 UI适当缩放和移动。您还希望能够轻松在测试期间更改分辨率,以便确保 UI 在显示窗口变形时定位正确。

If you build to a single resolution/aspect ratio, the UI will be much easier to build as you won’t have to make sure all the elements maintain their relative position at multiple aspect ratios. However, if you build a game that will run at multiple resolutions/aspect ratios (for example, a mobile project or a web game that scales within a window), you want your UI to scale and move appropriately. You’ll also want to be able to easily change the resolution during testing so that you can make sure the UI is positioned appropriately as its display window morphs.

即使您允许分辨率和宽高比发生变化,您仍应确定默认分辨率。此默认分辨率代表理想设计的分辨率。这将是您的初始设计和 UI 布局所基于的分辨率,因此如果分辨率或宽高比发生变化,UI 将尽可能保持相同的设计

Even if you will allow your resolution and aspect ratio to vary, you should still decide on a default resolution. This default resolution represents the resolution of your ideal design. This will be the resolution that your initial design and UI layout are based on, so if the resolution or aspect ratio varies, the UI will try to maintain the same design as best it can.

笔记

Note

由于目前销售的所有电视的宽高比均为 16:9,因此您为主机游戏制作的任何 UI 都应考虑 16:9 的宽高比进行开发

Since all televisions sold today have a 16:9 aspect ratio, any UI you make for a console game should be developed with a 16:9 aspect ratio in mind.

更改游戏视图的宽高比和分辨率

Changing the aspect ratio and resolution of the game view

可以在“游戏”选项卡中轻松切换不同的分辨率和宽高比。这将允许您查看你的 UI 如何在不同的分辨率和宽高比下缩放:

You can easily switch between different resolutions and aspect ratios in the Game tab. This will allow you to see how your UI scales at the different resolutions and aspect ratios:

  1. 如果你导航到“游戏”选项卡,你会看到“自由比例”字样。单击“自由比例”将显示一个菜单,其中显示各种比例和分辨率:
    图 1.4:从游戏视图中选择自由比例模式

    图 1.4:从游戏视图中选择自由比例模式

    此列表中显示的项目是您当前选择的构建目标最常见的宽高比和分辨率。在上图的屏幕截图中,我的构建目标是PC、Mac 和 Linux Standalone,因此最常见的显示器设置。如果我将我的构建目标更改为 iOS,我将看到流行的 iPhone 和 iPad 屏幕尺寸列表

    自由比例意味着游戏的宽高比将根据游戏视图的窗口缩放。因此,通过在游戏窗口上移动框架,您将更改宽高比。

  2. If you navigate to your Game tab, you will see the words Free Aspect. Clicking on Free Aspect will reveal a menu that shows various aspect ratios and resolutions:

    Figure 1.4: Selecting Free Aspect mode from the Game view

    The items displayed in this list are the most common aspect ratios and resolutions for the build target you currently have selected. In the preceding screenshot, my build target was PC, Mac & Linux Standalone, so the most common monitor settings are displayed. If I were to change my build target to iOS, I would see a list of popular iPhone and iPad screen dimensions.

    Free Aspect means that the game’s aspect ratio will scale relative to the window of the Game view. So, by moving the frame around on the Game window, you will change the aspect ratio.

  1. 通过将编辑器的布局设置为同时显示“屏幕”“游戏”选项卡,您可以轻松查看“自由比例”对游戏纵横比的影响。例如,将布局设置为2 x 3即可实现此目的。选择Unity 编辑器右上角的布局下拉菜单以更改布局。
    图 1.5:更改编辑器布局

    图 1.5:更改编辑器布局

    现在游戏和场景选项卡将可见在屏幕左侧。

    图 1.6:2×3 布局的结果

    图 1.6:2×3 布局的结果

  2. You can easily see the effects of Free Aspect on your game’s aspect ratio, by setting your Editor’s layout to one that shows both the Screen and Game tabs open simultaneously. For example, setting Layout to 2 by 3 will do this. Select the Layout dropdown in the top-right corner of the Unity Editor to change the layout.

    Figure 1.5: Changing the Editor Layout

    Now the Game and Scene tabs will both be visible on the left-hand side of your screen.

    Figure 1.6: Results of the 2 by 3 layout

  1. 现在,减少游戏选项卡的大小,以便是一个非常小的细长矩形。您将看到场景视图中的主摄像头现在也显示为一个非常小的细长矩形:
  2. Now, reduce the size of the Game tab so that it is a very small thin rectangle. You will see that the main camera in the Scene view is now also displaying as a very small thin rectangle:
图 1.7:在自由比例模式下调整游戏视图大小的结果

图 1.7:在自由比例模式下调整游戏视图大小的结果

Figure 1.7: Results of resizing the Game view in Free Aspect mode

  1. 你可以在下拉菜单中选择一个宽高比您会发现,当您重新缩放游戏窗口时,代表实际游戏的蓝色区域将保持您选择的比例,而黑条将填充任何额外的空间。相机也将保持该比例。
  2. You can select one of the aspect ratios in the dropdown and see that, as you rescale the game window, the blue area representing the actual game will maintain the ratio you selected and black bars will fill in any extra spacing. The camera will also maintain that ratio.
  3. 全高清 (1920x1080)将尝试模拟 1,920x1,080 分辨率。您为“游戏”选项卡设置的窗口很可能不够大,无法支持 1,920x1,080 像素;如果是这样,它将按以下屏幕截图所示缩放
  4. Full HD (1920x1080) will attempt to emulate the 1,920x1,080 resolution. It’s pretty likely that the window you have set for the Game tab is not big enough to support 1,920x1,080 pixels; if so, it will be scaled as indicated in the following screenshot:
图 1.8:游戏视图比例

图 1.8:游戏视图比例

Figure 1.8: Game view scale

  1. 如果您想要使用的分辨率或宽高比在分辨率下拉菜单中不可用,您可以通过选择下拉菜单底部的加号将自己的项目添加到此菜单。如果您想创建一个设置分辨率项目,请将类型设置为固定分辨率。如果您想创建一个设置宽高比项目,请将类型设置为宽高比
    图 1.9:添加新的分辨率或宽高比预设

    图 1.9:添加新的分辨率或宽高比预设

    例如,如果你想要制作一款让人回想起旧的 Game Boy 游戏,您可以添加 160x144像素预设:

    图 1.10:创建固定分辨率预设

    图 1.10:创建固定分辨率预设

  2. If the resolution or aspect ratio you want to use is not available in the resolution dropdown menu, you can add your own item to this menu by selecting the plus sign at the bottom of the dropdown. If you want to create a set resolution item, set Type to Fixed Resolution. If you want to create a set aspect ratio item, set Type to Aspect Ratio.

    Figure 1.9: Adding a new resolution or aspect ratio preset

    For example, if you wanted to make a game that was reminiscent of an old Game Boy game, you could add a 160x144 pixels preset:

    Figure 1.10: Creating a fixed resolution preset

  1. 点击“确定”后,新预设项将显示在列表底部。当你选择它,相机和可见游戏选项卡的区域将保持 160x144 分辨率的纵横比
  2. Once you hit OK, the new preset will item will be displayed at the bottom of the list. When you select it, the camera and visible area of the Game tab will maintain the aspect ratio created by a 160x144 resolution:
图 1.11:选择自定义预设

图 1.11:选择自定义预设

Figure 1.11: Selecting a custom preset

构建单一解决方案

Building for a single resolution

如果你创建计划在PC、Mac 和 Linux 独立目标平台上构建的游戏时,您可以强制分辨率始终相同。为此,请转到编辑|项目设置|播放器。您的检查器现在应显示以下内容:

If you are creating a game that you plan to build on the PC, Mac, & Linux Standalone target platform, you can force the resolution to always be the same. To do so, go to Edit | Project Settings | Player. Your Inspector should now display the following:

图 1.12:PC、Mac 和 Linux 独立播放器分辨率设置

图 1.12:PC、Mac 和 Linux 独立播放器分辨率设置

Figure 1.12: PC, Mac & Linux Standalone Player resolution settings

这里显示的平台可能会更多或更少;这取决于您使用 Unity 安装的模块。

You may have more or fewer platforms displayed here; it depends on the modules you have installed with Unity.

要在PC、Mac 和 Linux 独立游戏中强制使用特定分辨率,请取消选择默认为原始分辨率。系统将为您提供输入默认屏幕宽度默认屏幕高度的选项,您可以输入所需的分辨率值。然后,当您构建游戏时,它将以您指定的尺寸播放。

To force a specific resolution on a PC, Mac, & Linux Standalone game, deselect Default is Native Resolution. The option to input Default Screen Width and Default Screen Height will be made available to you and you can enter the desired resolution values. Then, when you build your game, it will play at the size you specified.

以下屏幕截图显示了强制 PC 游戏在 Game Boy Color 尺寸的窗口中播放的设置:

The following screenshot shows the settings for forcing a PC game to play in a window with Game Boy Color dimensions:

图 1.13:设置特定的 PC、Mac 和 Linux 独立播放器分辨率

图 1.13:设置特定的 PC、Mac 和 Linux 独立播放器分辨率

Figure 1.13: Setting a specific PC, Mac, & Linux Standalone Player resolution

您还可以使用WebGL构建强制使用特定分辨率。需要担心的选项较少,但总体概念是相同的。以下屏幕截图显示了在WebGL播放器设置中强制游戏以160 x 140显示的设置

You can also force a specific resolution with a WebGL build. There are fewer options to worry about, but the general concept is the same. The following screenshot shows the settings for forcing your game to display at 160x140 in the Player Settings for WebGL:

图 1.14:设置特定的 WebGL 分辨率

图 1.14:设置特定的 WebGL 分辨率

Figure 1.14: Setting a specific WebGL resolution

第 2 章中,我们将讨论如何为具有不同分辨率且无法预先定义的手机游戏设置分辨率属性。

In Chapter 2, we will discuss how to set the resolution properties for mobile games that have varying resolutions that you cannot pre-define.

概括

Summary

本章讨论了一些与 UI 相关的设计原则和术语。现在您应该能够区分 GUI 和 UI,并定义四种界面类型:叙事界面、空间界面、元界面和非叙事界面。此外,您还应该了解一些布局 UI 的基本规则以及如何在不同分辨率和宽高比下工作。

This chapter discussed some basic design principles and terminology related to UI. You should now be able to distinguish between GUI and UI and define the four types of interfaces: diegetic, spatial, meta, and non-diegetic. Additionally, you should understand some basic rules of laying out UI and how to work in different resolutions and aspect ratios.

下一章将扩展这些设计原则,并讨论设计手机游戏 UI 的一些重要考虑因素。

The next chapter will expand upon these design principles and look at some important considerations for designing UI for mobile games.

2

2

设计移动用户界面

Designing Mobile User Interfaces

移动界面设计最具挑战性的方面之一是移动设备的宽高比数量之多。移动设备包括手机和平板电脑。手机往往比平板设备长得多,而手机上完美适配的 UI 在平板电脑上可能会重叠或看起来被挤压。除了尴尬的分辨率之外,还有一些奇怪的怪癖会影响您的 UI,例如 iPhone 上的凹口和各种三星Galaxy 设备的折叠屏。

One of the most challenging aspects of designing for mobile interfaces is the sheer amount of possible aspect ratios of mobile devices. Mobile devices include phones and tablets. Phones tend to be much longer than tablet devices, and a UI that fits perfectly on a phone may overlap or look squished when put on a tablet. On top of awkward resolutions, there are also weird quirks that will affect your UI, such as the notch in the iPhone and the folding screens of the various Samsung Galaxy devices.

移动设备还具有不同的输入和交互方式。例如,在移动设备上,您可以同时触摸屏幕上的多个位置,但在 PC 上,您每次只能用鼠标点击一个位置。移动设备需要屏幕键盘和其他外围设备来执行您在游戏机或PC 游戏上执行的相同操作。

Mobile devices also have a different set of inputs and interactions. For example, on a mobile device, you can touch the screen at multiple locations simultaneously, but on a PC, you can only click with your mouse in one place at a time. Mobile devices require on-screen keyboards and other peripherals to perform the same actions that you might perform on a console or PC game.

在本章中,我将讨论围绕移动设备各种特性的设计考虑因素,并涵盖以下主题:

In this chapter, I will discuss the design considerations around the various quirks of mobile devices and cover the following topics:

  • 如何在各种移动设备分辨率下模拟你的游戏并以特定方向构建
  • How to simulate your game at various mobile resolutions and build at a specific orientation
  • 手机游戏按钮尺寸推荐
  • The recommended button sizes for mobile games
  • 利用隐形按钮创建点击区域
  • Utilizing invisible buttons to create tap areas
  • 根据拇指区域布局交互
  • Laying out interactions based on the thumb zone
  • 多点触控输入如何在移动用户界面中发挥作用
  • How multi-touch input plays a part in mobile UI
  • 何时使用加速度计和陀螺仪
  • When to use the accelerometer and gyroscope

技术要求

Technical requirements

对于本章,您需要 Unity 2020.3.26f1或更高版本。

For this chapter, you will need Unity 2020.3.26f1 or later.

笔记

Note

在描述移动界面时,我将主要关注 iOS 和 Android 操作系统的手机和平板电脑。不过,我偶尔也会提到微软,因为他们确实创造了一系列平板电脑设备,尽管它们运行的​​是 Windows 操作系统,但确实具有触摸功能。

When describing mobile interfaces, I will mostly focus on iOS and Android operating system phones and tablets. However, occasionally I will reference Microsoft, as they do create a series of tablet devices that, despite running Windows operating systems, do have touch capabilities.

设置分辨率、宽高比和方向

Setting the resolution, aspect ratio, and orientation

设计移动端 UI 时,你需要确保其合理且在各种分辨率下均清晰可见尺寸和长宽比。您可能还想允许不同的屏幕方向。在第 6 章中,我将讨论如何以机械方式开发可适应多种分辨率和布局的用户界面。但现在,我们先来回顾一下分辨率、宽高比和方向如何影响您的设计,以及如何使用各种屏幕设置查看您的游戏。

When you design a mobile UI, you’ll want to make sure it makes sense and is visible at various resolution sizes and aspect ratios. You also may want to allow for different screen orientations. In Chapter 6, I will discuss how you can develop user interfaces that scale to multiple resolutions and layouts, mechanically. But for now, let’s just review how resolution, aspect ratio, and orientation can affect your design and how to view your game with the various screen settings.

在游戏视图中设置分辨率

Setting the resolution in the Game view

回想一下第 1 章,我们看过你可以更改游戏视图的分辨率和宽高比。当您将游戏的构建设置更改为 iOS 或 Android 时,将为您提供一个新的预设列表。例如,在下图中,您可以看到将构建设置为 iOS时的可能性列表

Recall in Chapter 1, we looked at how you can change the resolution and aspect ratio of your Game view. When you change your game’s build settings to iOS or Android, a new list of presets will be provided to you. For example, in the following image, you can see the list of possibilities when the Build is set to iOS:

图 2.1:游戏视图中的 iOS 分辨率

图 2.1:游戏视图中的 iOS 分辨率

Figure 2.1: iOS resolutions in Game view

不会显示所有可能的 iOS 分辨率,但许多常见的较新版本。您可以在https://www.ios-resolution.com/找到更完整的列表。同样,在切换到 Android 版本时,并非所有 Android 分辨率都会列出,尤其是考虑到 Android 分辨率明显更多。但是,您可以找到有关 Android 屏幕分辨率的更多信息,请参阅https://developer.android.com/guide/practices/screens_support.xhtml#testing

It will not show all the possible iOS resolutions, but many of the common newer ones. You can find a more complete list at https://www.ios-resolution.com/. Similarly, not all Android resolutions will be listed when switching to an Android build, especially considering there are significantly more Android resolutions. However, you can find more information about Android screen resolutions at https://developer.android.com/guide/practices/screens_support.xhtml#testing.

设备模拟器

The Device Simulator

有时,设置游戏视图的方面不足以完全看到你的 UI 将如何显示设备。例如,iPhone X 中引入的有争议的刘海在游戏视图中不显示,这可能会给您最精心设计的 UI。但是,您可以使用设备模拟器查看这个缺口以及它如何与您的 UI 重叠

Sometimes, setting the Game view’s aspect isn’t sufficient to fully see how your UI will appear on a device. For example, the controversial notch introduced in the iPhone X doesn’t display in the Game view and can throw a huge wrench in your most carefully designed UIs. However, you can see this notch and how it overlaps with your UI using the Device Simulator.

要启用设备模拟器,请完成以下步骤:

To enable the Device Simulator, complete the following steps:

  1. 从包管理器下载设备模拟器包

    为此,请转到窗口|包管理器并在列表中搜索设备模拟器。您很可能最初不会在列表中看到它。如果没有看到它,请确保显示的包是来自Unity Registry 的包,如以下屏幕截图所示

  2. Download the Device Simulator Package from Package Manager.

    To do so, go to Window | Package Manager and search for Device Simulator in the list. It’s highly likely you won’t see it in the list initially. If you do not see it, make sure that the packages being displayed are those from Unity Registry, as shown in the following screenshot:

图 2.2:Unity Registry 包

图 2.2:Unity Registry 包

Figure 2.2: Unity Registry packages

  1. 如果你仍然没有在列表中看到它,你必须启用预览包。点击右上角的设置齿轮,选择高级项目设置,然后选择启用预览包,如图2.3所示
  2. If you still do not see it in the list, you have to enable preview packages. Click on the settings cog in the top-right corner, select Advanced Project Settings, and then select Enable Preview Packages, as shown in Figure 2.3:
图 2.3:启用预览包

图 2.3:启用预览包

Figure 2.3: Enable Preview Packages

  1. 一次可以在列表,安装它:
  2. Once you locate Device Simulator in the list, install it:
图 2.4:安装设备模拟器

图 2.4:安装设备模拟器

Figure 2.4: Install Device Simulator

  1. 现在已经下载了设备模拟器,您可以通过选择游戏下拉菜单,然后选择模拟器来更改游戏视图以反映模拟器,如以下屏幕截图所示
  2. Now that the Device Simulator is downloaded, you can change your Game view to reflect a simulator by selecting the Game dropdown and then selecting Simulator, as shown in the following screenshot:
图 2.5:选择模拟器视图

图 2.5:选择模拟器视图

Figure 2.5: Select Simulator view

  1. 现在轮到你可以选择多个设备从下拉菜单中选择要模拟的。例如,我可以选择 iPad 第 5 代或 iPhone X,其中 iPhone X 显示了可怕的缺口:
  2. Now you can select multiple devices from the dropdown to simulate. For example, I can select an iPad 5th generation or an iPhone X, where the iPhone X shows the dreaded notch:
图 2.6:模拟器上的不同设备

图 2.6:模拟器上的不同设备

Figure 2.6: Different devices on Simulator

  1. 此外,您还可以选择“安全区域”来查看布局UI 的最佳位置:
  2. Additionally, you can select Safe Area to see the best places to lay out your UI:
图 2.7:安全区域

图 2.7:安全区域

Figure 2.7: Safe Area

为特定方向建造

Building for a specific orientation

你可能已经注意到,在图 2.1,每个都有横向和纵向选项。分辨率。这样,您就可以根据开发过程中想要支持的方向来查看游戏

You may have noticed in Figure 2.1 that there are both landscape and portrait options for each resolution. This allows you to view your game depending on which orientation you want to support while developing.

针对移动设备构建时,您无法指定分辨率和宽高比,而是必须支持所有分辨率和宽高比。不过,您可以在移动设备上选择屏幕方向。有两种不同的方向:横向 纵向

When building for mobile devices, you can’t specify resolution and aspect ratio and will instead have to support all resolutions and aspect ratios. However, you can choose between screen orientations on mobile devices. There are two different orientations: Landscape and Portrait.

宽度大于高度的游戏被称为横向分辨率。高度大于宽度的游戏被称为纵向分辨率。例如,16:9 的宽高比是横向分辨率,9:16 的宽高比是纵向分辨率,如图所示:

Games built so that they are wider than they are tall are said to have landscape resolution. Games built taller than they are wide are said to have portrait resolution. For example, a 16:9 aspect ratio would be a landscape resolution, and a 9:16 aspect ratio would be a portrait resolution, as illustrated:

图 2.8:横向与纵向

图 2.8:横向与纵向

Figure 2.8: Landscape versus portrait Orientation

因此,虽然您无法选择移动游戏的确切宽高比,但您可以选择方向,从而强制宽高比变宽或变高。您可以通过导航到编辑|项目设置|播放器设置并选择移动设备来设置方向。如果您同时为 iOS 和 Android 构建,则不会必须为两者设置这些属性。从以下屏幕截图中可以看到, Default Orientation属性旁边的星号表示这些设置在多个平台之间共享

So, while you can’t choose the exact aspect ratio your mobile game will build to, you can choose the orientation, which forces the aspect ratio to be either wider or taller. You can set the orientation by navigating to Edit | Project Settings | Player Settings and selecting the mobile device. If you are building for both iOS and Android, you will not have to set these properties for both. As you can see from the following screenshot, the asterisk next to the property of Default Orientation states that the settings are shared between multiple platforms:

图 2.9:移动设备的分辨率和方向设置

图 2.9:移动设备的分辨率和方向设置

Figure 2.9: Resolution and Orientation Settings for mobile

您可以将默认方向设置为自动旋转或其他旋转之一,如下所示:

You can set the Default Orientation to either Auto Rotation or one of the other rotations, as shown:

图 2.10:移动设备的方向选项

图 2.10:移动设备的方向选项

Figure 2.10: Orientation Options for mobile

Unity 将以下方向定义为以下旋转:

Unity defines the following orientations as the following rotations:

图 2.11:移动端方向旋转

图 2.11:移动端方向旋转

Figure 2.11: Mobile orientation rotations

当你选择除自动旋转之外的旋转作为默认方向,游戏将仅在设备上以该方向播放。如果选择自动旋转,您将可以在多个方向之间进行选择:

When you select a rotation other than Auto Rotation as the Default Orientation, the game will only play at that orientation on the device. If you select Auto Rotation, you will have the option to select between multiple orientations:

图 2.12:自动旋转选项

图 2.12:自动旋转选项

Figure 2.12: Auto Rotation options

在大多数情况下,最好只选择横向或纵向而不是全部四个方向。通常,允许所有四个方向会导致游戏 UI 无法正确缩放。

In most cases, it is best to choose only the Landscape orientations or only the Portrait orientations, but not all four. Generally, allowing all four orientations will cause issues with the game›s UI’s ability to scale appropriately.

玩家往往更喜欢轮换游戏(尤其是像我一样喜欢玩当他们躺在床上玩手机充电时,手机会被迫面向一个特定的方向,因此除非你有充分的理由停止旋转,否则最好同时启用纵向纵向上下横向向右横向向左

Players tend to prefer to be able to rotate their games (especially if they’re like me and like to play games in bed while their phone is charging, thus being forced to face a specific direction), so unless you have a good reason to stop rotation, it’s a good idea to enable both Portrait and Portrait Upside Down or Landscape Right and Landscape Left.

建议按钮尺寸

Recommended button sizes

在创建移动游戏时,几乎所有交互都是通过按钮和屏幕点击来控制的。PC 或主机游戏上尺寸合理的按钮可能太小适用于手机游戏。因此,您需要确保按钮的设计在较小的屏幕上仍然可见,并且足够大以便手指触摸。

When creating a mobile game, pretty much all of your interactions are controlled by button and screen taps. Buttons that are a reasonable size on a PC or console game may be too small for a mobile game. Therefore, you’ll want to make sure to design your buttons so that they are still visible on the smaller screen and large enough to be touched by a finger.

Apple、Google 和 Microsoft 在设计其设备时,都对按钮可触摸区域的大小有具体的建议。Apple 建议按钮为 44 点 x 44 点。Google 建议按钮为 48 dp x 48 dp,两个按钮之间有 8 dp 的间距。Microsoft 建议按钮为 9 mm x 9 mm,两个按钮之间有 2 mm 的填充

Apple, Google, and Microsoft all have specific recommendations for the size of a button’s touchable area when designing for their devices. Apple recommends that buttons be 44 points x 44 points. Google recommends 48 dp x 48 dp with 8 dp spacing between two buttons. Microsoft recommends 9 mm x 9 mm with 2 mm padding between two buttons.

您可以在以下位置找到有关为每个移动平台设计触摸/点击区域的信息

You can find information about designing touch/hit areas for each mobile platform at the following locations:

令人恼火的是,所有这些建议都采用不同的测量单位。那么,这些数字在设计方面意味着什么呢?你如何确保你的按钮是 9 毫米 x 9 毫米或 44 点 x 44 点?为什么他们用不同的单位来谈论这些测量值?这几乎就像他们都是竞争对手,不想很好地合作!要回答这些问题,让我们首先看看各种测量单位代表什么:

Annoyingly, all of these recommendations are in different units of measurement. So, what do these numbers even mean in terms of design? How do you make sure your buttons are 9 mm x 9 mm or 44 points x 44 points? And why are they talking about these measurements in different units? It’s almost like they are all competitors and don’t want to work nicely together! To answer these questions, let’s first look at what the various units of measurement represent:

  • 点( pt ) 用于测量屏幕上的物理测量值。1 点等于 1/72 国际英寸或 0.3528 毫米。它主要用于排版和印刷媒体。使用Illustrator等程序时,以点为单位创建对象,然后以 72 ppi 导出图像会使像素和点的大小相同。点和像素并不相同,除非以72 ppi 导出。
  • A point (pt) is used to measure what represents a physical measurement on a screen. 1 point is 1/72 of an international inch or 0.3528 mm. It is primarily used in typography and print media. When working with a program such as Illustrator, creating an object in points and then exporting your image at 72 ppi makes pixels and points the same size. Points and pixels are not the same, except when exporting at 72 ppi.
  • 密度独立像素( dp ),发音为“dips”,是一种测量单位,旨在在不同dpi每英寸点数)值的屏幕上保持一致大小的项目。密度独立像素测量 160 dpi 屏幕上 1 像素的大小。使用这种转换就像说在 160 dpi 的屏幕上会显示此尺寸,在任何其他屏幕上也应显示完全相同的物理尺寸。您可以在https://developer.android.com/training/multiscreen/screendensities上阅读有关密度独立像素的更多信息
  • Density-independent pixels (dp), pronounced “dips”, is a unit of measurement that was created to maintain consistently sized items on screens with different dpi (dots per inch) values. A density-independent pixel measures the size of 1 pixel on a 160 dpi screen. Using this conversion is like saying it would appear at this size on a 160 dpi screen, and it should appear as the exact same physical size on any other screen. You can read more about density-independent pixels at https://developer.android.com/training/multiscreen/screendensities.
  • 当使用毫米( mm ) 来描述按钮尺寸时,该尺寸是按钮在屏幕上的物理表示。因此,如果您拿一把尺子并将其举到屏幕上,它将与此测量单位一致。
  • When millimeters (mm) are used to describe a button size, the size is a physical representation of the button on the screen. So, if you were to take a ruler and hold it up to the screen, it would be consistent with this unit of measurement.

好的,它们都代表屏幕上的物理测量单位。这样事情就简单多了。让我们将所有这些值都转换为毫米,这样我们就可以在一个更容易概念化的测量单位中比较它们。我还将把它们转换为点,因为您可以在程序(例如 Illustrator)中使用点来创建按钮艺术

OK, so they all represent some physical unit of measurement on the screen. That makes things a little easier. Let’s convert all of these values to millimeters so we can compare them in a unit of measurement that is a bit easier to conceptualize. I’m also going to convert them to points, since you can use points in a program, such as Illustrator, to create your button art.

如果您需要转换任何这些测量单位,但又不太喜欢做数学题,那么谷歌搜索“将点转换为毫米”将会为您带来一个不错的转换计算器

If you need to convert any of these units of measurement and you’re not too keen on the idea of doing math, googling convert points to mm will bring up a nice conversion calculator for you.

您还可以使用以下转换工具 - 它对于在所有不同的测量单位之间切换非常方便http://angrytools.com/android/pixelcalc/

You can also use the following converter tool – it is really handy for bouncing between all of the different units of measurement: http://angrytools.com/android/pixelcalc/

在下图中,我将点的测量值四舍五入为最接近的整数,将毫米的测量值四舍五入为最接近的十分之一,以方便查看。我们可以使用此图像来比较不同的尺寸(图像已缩放,尺寸可能无法转换为其实际测量值):

In the following chart, I rounded to the nearest integer the measurement for points, and to the nearest tenth for millimeters, to make things easier. We can use this image as a way to compare the different sizes (the image has been scaled and the sizes may not translate to their real-world measurements):

图 2.13:建议的最小可点击按钮尺寸

图 2.13:建议的最小可点击按钮尺寸

Figure 2.13: Recommended minimum tappable button sizes

那么,你应该使用哪种尺寸?这取决于你。你不必使用他们的建议,但我个人会选择苹果的建议,因为它是最大的,因此符合另外两个建议。此外,按钮越大,越多的人能够轻松触摸它。在第 4 章中,我们将讨论如何设计我们的 UI,以便尽可能多的人能够与其交互。

So, which size should you use? It’s up to you. You don’t have to use their recommendations, but I personally go with the Apple recommendation since it is the largest and therefore meets the recommendations of the other two. Additionally, the larger the button is, the more people will be able to easily touch it. In Chapter 4, we will discuss designing our UI so that the maximum amount of people will be able to interact with it.

另一个考虑因素是,您的游戏是用拇指还是手指来玩的。如果游戏是用拇指来玩的,您需要更大的按钮,因为拇指更大!前面描述的数字是最低建议值,因此它们将用于手指点击,而不是拇指点击。

Another consideration is whether your game will be played with thumbs or with fingers. If the game will be played with thumbs, you’ll want bigger buttons because thumbs are bigger! The numbers described previously are minimum recommendations, so they would be used for a finger tap, not a thumb tap.

那么,如何确保游戏中的按钮始终是您想要的大小?Canvas Scaler组件!在第 6 章中,我们将讨论如何通过将Canvas Scaler组件的UI Scale Mode设置为Constant Physical Size来确保按钮具有指定的大小(无论分辨率如何) 。您可以选择将 Canvas 的测量单位设为毫米或点(以及其他一些单位)。

So, how do you ensure that your buttons are always the size you want in your game? The Canvas Scaler component! In Chapter 6, we will discuss how ensuring a button of a specified size, regardless of resolution, can be achieved by setting the Canvas Scaler component’s UI Scale Mode to Constant Physical Size. You have the option to have your Canvas’s measurement units be in millimeters or points (as well as a few other units).

在为移动设备进行设计时,我的建议是准备多台设备,以不同的分辨率进行测试。玩游戏,看看感觉如何。问问有比您的手小或大的手都可以玩。即使遵循了各个移动平台指定的最低准则,您仍会发现您的按钮太小,无法满足您的需要。

My recommendation when designing for mobile devices is to have multiple devices on hand to test at various resolutions. Play the game and see how it feels to you. Ask people with smaller and larger hands than yours to play. Even after following the minimum guidelines specified by the various mobile platforms, you may still find your buttons are too small for what you need.

Google 和 Microsoft 还指定了他们推荐的可见尺寸,因此只要按钮的点击区域符合推荐尺寸,您就可以使用较小的按钮图像。如果您想要一个视觉上较小但点击区域较大的按钮,请不要将按钮组件附加到小图片上,而是将其附加到较大的父点击区域,并将按钮的目标图像更改在我的艺术中。

Google and Microsoft also specify visible sizes that they recommend, so you can have a smaller button image as long as the button’s hit area is the recommended size. If you want a button that is smaller visually but has a larger hit area, instead of attaching the button component to the tiny piece of art, attach it to a larger parent hit area and change the target image of the button to the tiny art.

全屏/部分屏幕点击

Full screen/screen portion taps

许多手机游戏都只有一个输入,你可以点击屏幕上的任意位置来执行操作。例如,无尽跑酷游戏往往允许玩家点击或按住屏幕上的任意位置即可跳跃。要实现此目的,您只需必须添加一个覆盖整个屏幕的隐形按钮。如果您有另一个接收输入的 UI,则需要将其放在全屏按钮的前面,以便该按钮不会阻挡对其他UI 项的输入。

Many mobile games have a single input whereby you can tap anywhere on the screen to make an action happen. For example, endless runners tend to allow the player to tap or press and hold anywhere on the screen to jump. To achieve this, you only have to add an invisible button that covers the whole screen. If you have another UI that receives inputs, it needs to be in front of the full-screen button so that the button does not block the inputs to the other UI items.

有些游戏需要你点击屏幕的特定区域来执行特定操作。例如,我创建了我在博士论文中设计了一款名为 Sequence Seekers 的游戏。这款游戏包含一个下山模式,玩家必须点击屏幕的左侧或右侧才能在游戏中向左或向右移动。我通过添加覆盖屏幕两半的隐形按钮来实现这一点,如下所示:

Some games require that you tap on specific regions of the screen to perform specific actions. For example, I created a game called Sequence Seekers for my doctoral dissertation. This game included a down-the-mountain mode in which the player had to tap the left or right-hand side of the screen to move left or right in the game. I achieved this by adding invisible buttons that covered the two halves of the screen, as shown here:

图 2.14:使用隐形按钮创建标签区

图 2.14:使用隐形按钮创建标签区

Figure 2.14: Using invisible buttons to create tab zones

第 9 章和第 11 章中,我们将讨论如何实现这样的按钮以及如何实现浮动方向d 操纵杆。

In Chapter 9 and Chapter 11, we’ll discuss how to implement such buttons as well as how to implement floating D-Pads and joysticks.

拇指区域

The thumb zone

在设计移动游戏时,考虑玩家如何握持设备非常重要。你不需要想要将 UI 放在玩家难以触及的区域。玩家往往喜欢用一只手握持和玩游戏。并非所有游戏都允许这样做,但如果可能的话,您希望让玩家这样做。您如何知道您的 UI 是否位于拇指可触及的区域?将 UI 放在拇指区域!本质上,拇指区域是玩家用一只手握持手机时可以轻松触及的手机区域。您可以通过握住手机并轻松移动拇指而无需移动手来找到特定手机上的拇指区域。

When designing a mobile game, it’s important to consider how the player will hold the device. You don’t want to put your UI in areas that will be difficult for the player to reach. Players tend to prefer to hold and play with one hand. Not all games allow for this, but if possible, you want to allow your players to do so. How do you know whether your UI is in an area that’s reachable by the thumb? Put the UI in the thumb zone! Essentially, the thumb zone is the area of the phone that is comfortable for the player to reach when holding the phone with one hand. You can find the thumb zone on your particular phone by holding the phone and easily moving your thumb around without having to move your hand.

以下博客文章对拇指区域进行了非常好的解释,并提供了一个方便的(无双关语)模板,用于在各种设备上查找拇指区域:https://www.scotthurff.com/posts/how-to-design-for-thumbs-in-the-era-of-huge-screens/

The following blog post offers a really great explanation of the thumb zone, along with a handy (no pun intended) template for finding the thumb zone on various devices: https://www.scotthurff.com/posts/how-to-design-for-thumbs-in-the-era-of-huge-screens/

电话链接中引用的内容有些陈旧,但它仍然是互联网上与拇指区域相关的最佳资源之一。

The phones referenced in the link are a bit on the old side, but it is still one of the best resources related to the thumb zone available on the internet.

作为一个左撇子,我恳请你在用拇指设计游戏时,考虑让游戏用左手玩起来和用右手玩起来一样容易区域

As a lefty, I implore you to consider making the game as easy to play with the left hand as it is for the right when designing with the thumb zone in mind.

其他移动输入

Other mobile inputs

在为移动设备进行设计时,重要的是要记住,输入方式与电脑或主机游戏略有不同。移动设备上的大部分输入都是受控制的通过触摸屏、加速计或陀螺仪。这为您在创建移动游戏时提供了一组不同的设计选择。

When designing for mobile, it’s important to remember that the input works a little differently than it does with a computer or console game. Most of the input on a mobile is controlled by the touchscreen, accelerometer, or gyroscope. This opens up a different set of design choices for you when creating mobile games.

触摸屏设备通常可以访问多点触摸。您可以使用多点触摸进行不同类型的交互,但多点触摸最常见的用法是允许玩家捏合缩放。在第 8 章中,我们将讨论如何使用多点触摸输入在游戏中创建平移和捏合缩放功能。

Touchscreen devices can generally access multiple touches. You can use multi-touch for different types of interactions, but the most common usage of multi-touch allows the player to pinch-to-zoom. In Chapter 8, we will discuss how to use multi-touch input to create pan and pinch-to-zoom functionality in a game.

大多数移动设备都内置有加速度计,许多设备还配有陀螺仪。在描述它们的实际工作原理时,我们不必太过技术性,加速度计和陀螺仪之间的区别在于它们测量的内容。加速度计测量 3D 坐标系内的加速度,而陀螺仪测量旋转。我们将回顾如何使用加速度计和陀螺仪的示例参见第 8 章

Most mobile devices have a built-in accelerometer and many also have a gyroscope. Without getting too technical in describing how they actually work, the difference between the accelerometer and the gyroscope is what they measure. The accelerometer measures acceleration within the 3D coordinate system and the gyroscope measures rotation. We will review examples of how to use the accelerometer and gyroscope in Chapter 8.

设备特定资源

Device-specific resources

如果你在为移动设备制作 UI 时,您可能希望使用特定于设备的 UI 元素来保持一致的风格。您可以在以下位置找到用于为每个移动平台设计 UI 的各种艺术资产和模板地点:

If you are making a UI for a mobile device, you may want to use the device-specific UI elements to maintain a consistent style. You can find various art assets and templates for designing UI for each mobile platform at the following locations:

概括

Summary

为移动设备创建 UI 与为游戏机或电脑创建 UI 并无太大区别,但不同之处在于,您可以接受多个屏幕输入,还可以访问有关设备加速度计和陀螺仪的信息。此外,分辨率在游戏开发中起着重要作用,因为移动设备的分辨率和宽高比范围非常广泛。

Creating a UI for mobile devices isn’t too different from creating a UI for a console or computer, but it is different in that you can accept more than one screen input and can also access information about the device’s accelerometer and gyroscope. Additionally, resolution plays an important part in your game’s development, since mobile devices have a huge range of resolutions and aspect ratios.

在下一章中,我们将讨论为 XR 应用程序(包括 VR、MR和 AR)开发 UI 的设计注意事项。

In the next chapter, we’ll discuss the design considerations for developing UI for XR applications, including VR, MR, and AR.

3

3

设计 VR、MR 和 AR UI

Designing VR, MR, and AR UI

虚拟现实( VR )、混合现实( MR ) 和增强现实( AR ) 的用户界面( UI ) 设计研究范围广泛且不断发展。它是一项新兴技术,也是实践尚未完全界定;然而,有很多研究人员和开发人员在工作努力确定哪些是最佳做法。在此在本章中,我将总结一些普遍认同的为扩展现实( XR ) 体验设计 UI 的最佳实践

The study of user interface (UI) design for virtual reality (VR), mixed reality (MR), and augmented reality (AR) is expansive and ever-growing. It is an emergent technology, and best practices are not yet fully defined; however, there are a lot of researchers and developers working diligently to determine what those best practices are. In this chapter, I’ll summarize some of the generally agreed-upon best practices for designing a UI for extended reality (XR) experiences.

在本章中,我将讨论以下内容:

In this chapter, I will discuss the following:

  • 区分 XR、VR、MR 和AR 应用
  • Distinguishing between XR, VR, MR, and AR applications
  • 开发VR UI 的设计注意事项和最佳实践
  • Design considerations and best practices for developing UI for VR
  • 开发MR UI 的设计注意事项和最佳实践
  • Design considerations and best practices for developing UI for MR
  • 开发AR UI 的设计注意事项和最佳实践
  • Design considerations and best practices for developing UI for AR

什么是XR、VR、MR和AR?

What are XR, VR, MR, and AR?

在我们可以开始讨论VR、MR和AR的最佳实践,我们应该明确XR、VR、MR和AR的定义。

Before we can begin discussing best practices for VR, MR, and AR, we should clarify the definition of XR, VR, MR, and AR.

如果你仔细阅读本章,你可能会注意到,没有标题为“为 XR 设计 UI”的章节。这是因为 XR 涵盖了 VR、MR 和 AR!这是一个总称。事实上,本章更简洁的标题可能是“设计 XR UI”!XR 包括 VR、MR 和AR 技术:

You may notice, if you look ahead on this chapter, that there are no sections titled Designing UI for XR. That is because XR encompasses VR, MR, and AR! It is an umbrella term. In fact, a more concise title for this chapter could have been Designing XR UI! XR includes VR, MR, and AR technologies:

图3.1:XR技术的代表

图3.1:XR技术的代表

Figure 3.1: Representation of XR technologies

VR体验可能是最容易描述的。它们完全存在于虚拟世界中。整个物理世界都被遮挡,你唯一能看到的就是虚拟空间。这种空间通常非常身临其境,以至于大脑很难将其与现实区分开来。支持 VR 的设备可以覆盖用户的眼睛,完全阻挡现实世界中的视觉效果。如今支持 VR 的热门设备有 Meta(正式名称为 Oculus)Quest 系列、索尼 PlayStation VR 系列和 HTC Vive 系列。一些流行的 VR 游戏包括《半条命:Alyx》《Beat Saber》

VR experiences are perhaps the easiest to describe. They exist fully in the virtual world. The entire physical world is blocked from view, and the only thing you can see is the virtual space. This type of space can often be so immersive that the brain struggles to distinguish it from reality. Devices that facilitate VR fit over the user’s eyes to completely block out visuals in the real world. Popular devices today that support VR are the Meta (formally Oculus) Quest series, the Sony PlayStation VR series, and the HTC Vive series. Some popular examples of VR games include Half-Life: Alyx and Beat Saber.

MR叠加虚拟现实世界中的物品并允许您与它们互动。用户可以看到现实世界并与现实世界的物品和虚拟物品互动。MR 设备也可以以眼镜和耳机的形式戴在用户的脸上。它们要么不会完全阻挡用户的视线,要么允许视频直通,让用户通过镜头看到现实世界。如今,促进 MR 练习的流行设备有 Meta Quest 2(及以上)、Microsoft HoloLens 系列和 Magic Leap。MR 体验的一个流行示例是I Expect You To Die:Home Sweet Home

MR overlays virtual items in the real world and allows you to interact with them. The user can see the real world and interact with both real-world items and virtual items. MR devices are also worn on the user’s face in the form of glasses and headsets. They either do not fully block out the real world from the user’s view or they allow video passthrough to allow the user to see the real world through lenses. Popular devices today that facilitate MR exercises are the Meta Quest 2 (and above), the Microsoft HoloLens series, and Magic Leap. A popular example of an MR experience is I Expect You To Die: Home Sweet Home.

AR类似于MR 结合了现实世界和虚拟世界;然而,它的不同之处在于虚拟物品不会与现实世界互动。在 AR 中,虚拟物品只是覆盖在现实世界上,并不出现在与现实世界相同的空间中。此外,它不允许用户以感觉就像发生在他们自己的世界中的方式与虚拟物品互动。AR 互动通常发生在屏幕上,而 MR 互动发生在物理空间内。两者的区别很微妙,但你可以认为 AR 是与屏幕上出现的虚拟物品互动,而 MR 是与看起来像在你的空间中的物品互动。市场上有更多 AR 设备。智能手机和平板电脑是最受欢迎的 AR 推动者;然而,一些智能眼镜也能做到这一点。此外,世界各地都有 AR 屏幕、信息亭和装置。AR 游戏的流行例子包括Pokémon GO Ingress

AR is similar to MR in that it combines real and virtual worlds; however, it differs in that the virtual items do not interact with the real world. In AR, virtual items are simply overlaid on the real world and don’t appear within the same space as the real world. Also, it does not enable the user to interact with virtual items in a way that feels like it is happening in their world. AR interactions usually happen on a screen, while MR interactions happen within a physical space. The distinction is subtle, but you can consider AR as interacting with virtual items that appear on a screen while MR is interacting with items that appear as if they are in your space. There are significantly more AR devices available on the market. Smartphones and tablets are the most popular facilitators of AR; however, some smart glasses also do it. Additionally, there are AR screens, kiosks, and installations that can be found all over the world. Popular examples of AR games include Pokémon GO and Ingress.

笔记

Note

XR 的概念和区别有些微妙,我还没有完全深入研究在本节中。如果您想了解更多信息,我建议您研究虚拟连续体:https ://www.interaction-design.org/literature/topics/virtuality-continuum 。

There is some nuance to the ideas of XR and their distinctions that I have not fully delved into in this section. If you would like to learn more, I recommend researching the virtuality continuum: https://www.interaction-design.org/literature/topics/virtuality-continuum.

现在我们了解了 VR、MR 和 AR 之间的区别,让我们来看看为这些体验设计 UI 的一些最佳实践。

Now that we understand the difference between VR, MR, and AR, let’s look at some best practices for designing UI for these experiences.

为 VR 设计 UI

Designing UI for VR

通常,当你想到 UI 时,你会想到平视显示器( HUD )——屏幕上显示的 UI所有游戏玩法都离不开屏幕。但在 VR 中,没有像在游戏机上玩的游戏那样的屏幕。玩家感觉就像身临其境一样。完全沉浸在这个世界中,他们通过屏幕(镜头)体验 VR 游戏时,屏幕上的某些东西会变得模糊,无法看到,因为它正好位于他们眼睛上方。因此,VR UI 往往被放置在玩家沉浸其中的世界中。

Normally, when you think of UI, you think of a heads-up display (HUD)—UI that is on the screen in front of all gameplay. But in VR, there is no screen in the same sense as there is in games played on a console. The player feels as if they are fully immersed in the world, and putting something on the screen they are experiencing a VR game through (the lenses) would result in a fuzzy, unviewable blur since it would be right on top of their eyes. Due to this, VR UI tends to be placed within the world the player will be immersed in.

笔记

Note

在第 16 章中,我们将讨论在世界中创建具有物理表示的 UI 的步骤。

In Chapter 16, we will discuss steps around creating UI with a physical representation within the world.

虽然可能存在一些例外,但 VR 游戏中的 UI 通常放置在三个位置

While there may be some exceptions, there are three locations where a UI in a VR game is usually placed:

  • 以静态位置嵌入世界。玩家必须通过将角色移向虚拟位置来接近它。
  • Embedded within the world in a static position. The player must approach it by moving their avatar toward its virtual location.
  • 嵌入在玩家面前一定距离的世界内。UI 随玩家移动,无论玩家将头转向何处,始终处于相同的相对位置。或者玩家的头像在空间中不移动,UI 也不移动。
  • Embedded within the world a set distance in front of the player’s face. The UI moves with the player and is always in the same relative location regardless of where the player turns their head. Or the player’s avatar does not move in space and the UI also does not move.
  • 附着在玩家的手上——UI 显示为附着在玩家角色的虚拟手/手臂上的一些外围设备。
  • Attached to the player’s hands—the UI appears as some peripheral device attached to the player’s avatar’s virtual hands/arms.

哪一个您选择的三个展示位置取决于游戏设计会影响游戏体验,但也会受到你是否希望玩家能够与 UI 交互以及如何交互的影响。本质上,你要确保玩家能够看到 UI 并与之交互(如果需要)。

Which of the three placements you choose depends on your game’s design but can also be affected by if and how you want the player to be able to interact with the UI. In essence, you want to make sure the player can see the UI and interact with it (if necessary).

让我们从设计视觉 UI 的考虑开始我们的 VR UI 设计探索。

Let’s start our VR UI design exploration with considerations for designing visual UI.

视觉 UI 位置和注意事项

Visual UI placement and considerations

正如我之前所说,设计 VR 游戏体验并没有一套明确的规则因为该技术仍被认为是新兴技术,研究人员和开发人员仍在学习最佳实践是什么。

As I’ve said before, there is not an explicit set of rules for designing VR gameplay experiences as the technology is still considered emergent and researchers and developers are still learning what those best practices are.

创建时如果 UI 始终与玩家保持特定距离,则将其放置在玩家可以看到的地方非常重要。玩家可以看到物品并不意味着看到它会很舒服。例如,虽然玩家可以看到其周边范围内的物品,但这不是放置物品的舒适位置UI。下图显示了人的视野( FOV ) 的一般角度,其中灰色区域为最大视角:

When creating a UI that will be a specific distance from the player at all times, it is important to put it in a place that the player can see. Just because a player can see an item doesn’t mean it is comfortable to see it. For example, while a player can see items in their peripheral range, this would not be a comfortable place to put the UI. The following diagram shows the general angle for a person’s field of view (FOV), where the gray areas are maximum viewing angles:

图 3.2:人类视野的概括

图 3.2:人类视野的概括

Figure 3.2: Generalization of the human field of view

笔记

Note

上图中描述的角度是一般准则,并非一成不变。它们会根据用户的视力、是否要要求他们转动眼睛(而不是头部)以及所使用的设备而变化。

The angles described in the previous diagram are general guidelines and not set in stone. They will change depending on the user’s eyesight, whether or not you want to require them to rotate their eyes (and not their head), and the device you are using.

记住当玩家移动头部时,摄像头也会随之移动。因此,如果你的 UI 被放置在鼓励玩家转动头部的位置头部并锚定在头部,UI 将随头部移动。因此,请尝试将任何锚定 UI 放置在图 3.2描述的区域中。

Keep in mind that when a player moves their head, the camera moves with it. So, if your UI is placed in a position that encourages them to rotate their head and is anchored to their head, the UI will move with their head. So, try to place any anchored UI in the area described in Figure 3.2.

除了相对于玩家眼睛的角度之外,您还必须考虑与玩家眼睛的距离。如果太近或太远,都会引起眼睛疲劳。尽量让任何视觉 UI(尤其是必须阅读的文本)距离玩家眼睛 1.3 到 3 米以内

On top of the angle relative to the player’s eye, you must also consider the distance from the player’s eye. If it’s too close or too far, it will cause eye strain. Try to keep any visual UI, especially text that must be read, within 1.3 to 3 in-game meters from the player’s eyes.

如果玩家处于静止位置,则在距离他们一定距离的地方创建静态弯曲 UI 总是可以帮助玩家舒适地移动头部来查看UI。

If the player is in a stationary position, creating a static curved UI a set distance away from them always helps the player comfortably move their head around to view the UI.

我想讨论的最后一个关于视觉 UI 的考虑因素是文本大小。虽然建议可能因来源而异,但我看到的一致引用(没有双关语)是,UI 应以每米 2.32 厘米的高度显示。因此,如果它显示在 1 米外,它应该显示 2.32 厘米高。如果它显示在 2 米外,它应该显示 4.64 厘米高。就我个人而言,我的视力很差,我倾向于让我的 UI 文本比这大一点,这样我就可以轻松查看。我的建议是让多个眼力不同的人玩你的游戏,并告诉你 UI 是否舒适

The last consideration for visual UI I want to discuss is text size. While the recommendation may vary depending on your source, the one I see consistently cited (no pun intended) is that UI should appear a multiple of 2.32 cm tall for every meter away the UI displays. So, if it displays 1 meter away, it should appear 2.32 cm tall. If it displays 2 meters away, it should appear to be 4.64 cm tall. Personally, I have very bad eyesight, and I tend to make my UI text a bit bigger than this so I can easily view it. My recommendation is to have multiple people with varying eye strengths play your game and tell you if the UI is comfortable or not.

现在我们已经回顾了在 VR 空间中设计视觉 UI 的注意事项,让我们回顾一下可交互 UI 的一些注意事项。

Now that we’ve reviewed considerations for designing visual UI in VR space, let’s review some considerations for interactable UI.

可交互 UI 位置和注意事项

Interactable UI placement and considerations

在 VR 中,可交互 UI 可以通过几种不同的方式实现,这取决于体验使用控制器或手部追踪。

Interactable UI is achieved in a few different ways in VR, and it depends on whether the experience uses a controller or hand tracking.

使用时控制器,以下是与 UI 交互的常见方式

When using a controller, here are common ways in which UI can be interacted with:

  • 玩家只需按下按钮即可进行交互 - 例如,按下菜单按钮即可使菜单按钮出现和消失。
  • The player simply presses a button to perform an interaction—for example, pressing a menu button to make a menu button appear and disappear.
  • 玩家使用射线选择一个 UI 物品。玩家指向 UI 物品,射线从他们的手中出现,然后他们按下按钮与物品进行交互。
  • The player selects a UI item with a ray. The player points toward the UI item, a ray appears from their hand, and they then press a button to interact with the item.
  • 玩家的角色虚拟地接近 UI 项目,并与其进行交互。这包括用虚拟手按下虚拟空间中的按钮或抓取虚拟物品。
  • The player’s avatar virtually approaches a UI item, and the player interacts with it. This includes pressing buttons in virtual space with a virtual hand or grabbing virtual items.

使用手部追踪时,UI 可以采用以下常见方式进行交互:

When using hand tracking, here are common ways in which UI can be interacted with:

  • 玩家做出一些手势,UI 交互就完成了。例如,竖起大拇指的手势可能会导致菜单出现或消失。
  • The player performs some gesture, and a UI interaction is completed. For example, making a thumbs-up gesture could cause a menu to appear or disappear.
  • 玩家用手指指向一个物品,然后从手指上出现一道射线。然后他们完成其他手势来确认选择。
  • The player points with their finger at an item, and a ray appears from their finger. They then complete some other gesture to confirm the selection.
  • 玩家的角色虚拟地接近 UI 项目,并与其进行交互。这包括用虚拟手按下虚拟空间中的按钮或抓取虚拟物品。
  • The player’s avatar virtually approaches a UI item, and the player interacts with it. This includes pressing buttons in virtual space with a virtual hand or grabbing virtual items.

当通过射线与 UI 交互时,玩家不需要物理上能够接触到它。但如果你想创建玩家的化身必须虚拟交互的 UI,你将需要确保它在玩家触手可及的范围内水平和垂直方向均可。可达性因素包括

When interacting with UI via a ray, it is not necessary for the player to physically be able to reach it. But if you want to create UI that the player’s avatar must virtually interact with, you will need to make sure it is within reach of the player both horizontally and vertically. Factors for reachability include the following:

  • 游戏是以静止模式进行还是玩家需要四处移动
  • Whether the game is played in a stationary mode or if the player is expected to move around
  • 玩家的角色是否可以移动
  • Whether the player’s avatar can move around or not
  • 球员身高
  • The player’s height
  • 如果玩家坐着或站着
  • If players are sitting or standing
  • 球员的一般行动能力和残疾情况
  • The player’s general mobility and disabilities

一个好的经验法则是,将物品放置在距离玩家静止位置 0.5 到 0.75 米之间,以确保玩家能够够到物品。就像您要让视力水平不同的人测试您的视觉 UI 一样,您也需要让身高和能力不同的人测试您的 UI。

A good rule of thumb is to try to make sure players can reach your items by placing them between 0.5 and 0.75 meters from their stationary position. Just as you want to test your visual UI with people with various levels of sight, you will want to test your UI with people of various heights and abilities.

无论是使用控制器还是手势追踪,您都需要确保所选的互动方式不会让玩家感到疲劳。要求他们做出多个手势或长时间举起双手可能会导致疲劳和紧张,而您肯定不希望玩家承受这种压力

With both controllers and hand tracking, you want to make sure that whatever interaction you choose is not tiring for the player. Requiring them to perform multiple gestures or hold their hands up for a significant amount of time can cause fatigue and strain that you don’t want to inflict on your player.

这就是我想讨论的关于为 VR 设计 UI 的主要考虑因素。但是,还有更多的研究和信息可供您探索!有关为 VR 设计 UI 的更多信息,我建议查看本章末尾提供的资源。

That concludes the major considerations for designing UI for VR that I wish to discuss. However, there is a lot more research and information out there for you to explore! For more information on designing UI for VR, I recommend checking out the resources provided at the end of the chapter.

现在我们已经了解了为 VR 设计 UI,接下来让我们了解一下为 MR 设计 UI。

Now that we’ve looked at designing UI for VR, let’s look at designing UI for MR.

为 MR 设计 UI

Designing UI for MR

为 MR 设计 UI 与为 VR 设计 UI 非常相似,因此上一节中讨论的大部分内容都适用。不过,为 MR 设计 UI 确实有一些额外的注意事项。

Designing UI for MR is extremely similar to designing UI for VR, so most of what I discussed in the previous section translates. However, designing UI for MR does have a few extra caveats.

主要VR 和 MR 的 UI 设计之间的区别在于 MR 结合了玩家的物理空间和他们周围的物品。在玩 VR 时,人们通常被假设周围有宽阔的空间——这样当他们胡乱挥舞时,就不会撞到任何东西而伤到自己。MR 体验则恰恰相反——玩家通常被鼓励在现实世界中接触家具、墙壁和其他物品

The main distinction between designing UI for VR and MR is MR incorporates the player’s physical space and items around them. When playing VR, people generally are assumed to have a wide-open space around them—so that when they flail about recklessly, they don’t hit anything and hurt themselves. The opposite tends to be true with MR experiences—players are generally encouraged to be around furniture, walls, and other items in their real world.

虚拟放置的 UI 不仅必须能够通过玩家访问,还不能与玩家世界中存在的事物相交。您不能让玩家按下桌子内的按钮或查看墙壁后面的屏幕。因此,在为 MR 体验设计 UI 时,放置 UI 时必须考虑玩家的空间,而不仅仅是玩家的身体

The UI being placed virtually not only has to be accessible via the player, but it also cannot intersect with things that exist within their world. You can’t have the player pressing buttons that are inside their desk or viewing screens that are behind their walls. So, when designing UI for an MR experience, you must consider the player’s space and not just the player’s body when placing your UI.

解决此问题的方法包括允许玩家选择其 UI 的显示位置、将其显示在技术检测到的表面上(如何检测在很大程度上取决于玩家使用的设备),或将其附加到玩家身上。

Ways around this include allowing the player to pick where their UI displays, displaying it on top of surfaces that the technology detects (how it is detected is highly dependent on the device the player is using), or attaching it to the player.

MR 是一个比 VR 更为新兴的领域,尤其是在视频游戏领域。到目前为止,MR 主要用于工业和医疗目的,并且通常在高度受控的空间中进行。对于在家中拥有无限家具和物体摆放可能性的游戏玩家来说,MR 仍处于起步阶段,直到最近才可通过 Microsoft HoloLens 在 Unity 中实现。然而,Meta Quest 2 增加了在 Unity 中创建 MR 游戏的功能,而 Meta Quest 3 计划提供更多精心策划和完善的 MR 支持。此外,Magic Leap 已与 Unity 合作,以支持 MR 开发。因此,未来几年您可能会看到更多有关 MR UI 的最佳实践和技术。

MR is an even more emergent space than VR, especially in the video game field. MR has mostly been used for industrial and medical purposes up to this point and is usually performed in a highly controlled space. MR for a gamer in their home with infinite possibilities of furniture and object placement is still in its infancy and until recently was only achievable in Unity via the Microsoft HoloLens. However, the Meta Quest 2 has added the ability to create MR games in Unity, and Meta Quest 3 plans to have more curated and polished MR support. Additionally, Magic Leap has partnered with Unity to allow for MR development. So, you will likely see more best practices and technology around MR UI in the coming years.

笔记

Note

有关使用 Unity 和 HoloLens 进行开发的信息,请参阅https://learn.microsoft.com/en-us/windows/mixed-reality/develop/unity/unity-development-overview?tabs=arr%2CD365%2Chl2

For information about developing with Unity and the HoloLens, see https://learn.microsoft.com/en-us/windows/mixed-reality/develop/unity/unity-development-overview?tabs=arr%2CD365%2Chl2.

信息有关 Quest 3 计划的 MR 功能,请参阅https://developer.oculus.com/blog/build-the-next-generation-of-vr-mr-with-meta-quest-3/

For information about MR capabilities planned for Quest 3, see https://developer.oculus.com/blog/build-the-next-generation-of-vr-mr-with-meta-quest-3/.

信息有关使用 MagicLeap 进行开发的信息,请参阅https://ml1-developer.magicleap.com/en-us/learn/guides/unity-overview

For information about developing with MagicLeap, see https://ml1-developer.magicleap.com/en-us/learn/guides/unity-overview.

遗憾的是,由于 MR 是 Unity 引擎中一个新兴领域,我无法透露更多关于为其设计 UI 的信息。不过,AR 已经存在了相当长一段时间,所以现在让我们来探索一下吧!

Sadly, since MR is such a new and emergent space within the Unity engine, I cannot say much more about designing UI for it. However, AR has been around for quite some time, so let’s explore it now!

为 AR 设计 UI

Designing UI for AR

请记住,AR 与 MR 的区别在于,AR 往往出现在覆盖MR 更像真实世界,而 MR 则更像沉浸式,在现实世界中。因此,AR 体验中的互动项目将显示在屏幕上,而不是在现实世界中。

Remember, that what distinguishes AR from MR is AR tends to be on a screen that overlays the real world, while MR appears more immersed and inside the world. So, interactive items for AR experiences will be displayed on the screen, not within the world.

设计AR 的 UI 高度依赖于用于增强现实的设备。为了清晰起见,我将重点讨论为手机开发的 AR 游戏,而不是试图泛泛而谈,包括分期付款和自助服务终端等内容。

Designing UI for AR is highly dependent on the device that is being used to augment reality. For the sake of clarity, I am going to focus my discussion on AR games developed for cell phones rather than trying to speak universally and include things such as installments and kiosks.

在移动设备或触摸屏上为 AR 应用设计 UI 时,您需要遵循第 2 章中概述的规则。最佳做法是将大多数非增强数字项目(即 HUD UI)放置在屏幕的外边缘。然后,增强数字项目将出现在屏幕中间。这将导致增强的项目让世界成为焦点。

When designing UI for AR app on a mobile or touchscreen, you will want to follow the rules outlined in Chapter 2. Best practice is to place most non-augmented digital items (that is, the HUD UI) on the outer edges of the screen. Then, augmented digital items will appear in the middle of the screen. This will cause items that are augmenting the world to be the focus.

概括

Summary

在本章中,我们讨论了开发 XR 体验的一些基本设计注意事项。由于我们体验 VR、MR 和 AR 的方式各不相同,因此我们回顾了在设计每种类型的体验时应牢记的注意事项。

In this chapter, we discussed some basic design considerations for developing XR experiences. Since the ways in which we experience VR, MR, and AR are all different, we reviewed considerations you should keep in mind when designing for each of these types of experiences.

XR 仍是一个新兴领域,因此目前还没有很多被广泛接受的标准。MR 尤其如此,最近才开始推出面向消费者的 MR 设备。不过,本章提供的信息应该可以帮助您开始进行 XR UI 设计,并让您开始思考 XR 空间与屏幕空间的区别。

XR is still an emergent space, so there are not yet many widely accepted standards. This is particularly true with MR, which just recently began having consumer-available MR devices. However, the information provided in this chapter should help you get started with XR UI design and get you started thinking about how the XR space differs from screen space.

在下一章中,我们将讨论通用设计和可访问性的概念,以及如何使您的用户界面更具包容性和用户友好性。

In the next chapter, we will discuss the concepts of universal design and accessibility and how you can make your user interfaces more inclusive and user-friendly.

进一步阅读

Further reading

您可以在此处找到有关学习如何为 XR 设计 UI 的更多资源

Further resources for learning about designing UI for XR can be found here:

4

4

UI 的通用设计和可访问性

Universal Design and Accessibility for UI

在设计界面时,您需要考虑可能与您的游戏互动的所有不同类型的玩家。您需要考虑他们的体型、年龄、当前情况、周围位置、输出设备、输入设备、认知、偏好、移动性水平等。您希望您的界面可供尽可能多的人使用。

When designing your interface, you want to consider all the different types of players who may interact with your game. You want to consider their size, their age, their current situation, their ambient location, their output device, their input device, their cognition, their preferences, their mobility levels, and more. You want your interface to be usable by as many types of people as possible.

在设计过程开始时考虑这些因素非常重要,因为在最初设计 UI 时考虑这些因素比在 UI 已经设计、构建和实现后再加入它们要容易得多。

It’s important to consider these factors at the beginning of the design process as designing your UI initially with these things in mind is significantly easier than incorporating them later when your UI is already designed, built, and implemented.

在本章中,我们将讨论以下主题:

In this chapter, we will discuss the following topics:

  • 什么是通用设计和无障碍设计?
  • What are universal design and accessible design?
  • 通用设计原则
  • Universal design principles
  • 无障碍设计
  • Accessibility design

什么是通用设计和无障碍设计?

What are universal design and accessible design?

羅納德·梅斯创造了通用设计这个术语,并对其定义如下:

Ronald Mace coined the term universal design and defined it as follows:

“产品和环境的设计应尽可能适合所有人使用,而无需进行改装或专门设计。”

“The design of products and environments to be usable by all people, to the greatest extent possible, without the need for adaptation or specialized design.”

通用设计是设计可供所有人使用的产品的概念,无论其年龄、体型、偏好、能力、残疾、状况或情况如何。它不专注于为特定人群设计,而是专注于设计可供最广泛人群使用的产品。通用设计的概念最初侧重于建筑,现已扩展到包括所有类型产品的设计,包括视频游戏。无障碍设计是一种专门针对特定人群的设计注重有障碍和残疾的人的可用性,是通用设计的一个子集。

Universal design is the concept of designing products that are usable by all people regardless of age, size, preference, ability, disability, condition, or situation. It does not focus on designing for a specific group of people but instead focuses on designing products that are useable by the widest range of people. While initially focused on architecture, the concepts of universal design have been expanded to include the design for all types of products, including video games. Accessible design is a design that specifically focuses on usability by those with impairments and disabilities and is a subset of universal design.

为了使设计有效,必须做出有目的的决定,以确保设计既实用又对所有类型的人都有用。在设计用户界面时,您应该从设计过程一开始就考虑如何让所有人都能使用和访问它。如果您从一开始就考虑到这些因素,那么设计一个普遍可用且可访问的用户界面要比在现有界面上改造功能容易得多。

For a design to be effective, purposeful decisions must be made to ensure that the design is both functional and useful to all types of people. When designing a user interface, you should consider how to make it usable and accessible to all people from the beginning of the design process. It is much easier to design a universally usable and accessible UI if you start with these considerations in mind than it is to retrofit features on top of pre-existing interfaces.

在以下章节中,我将讨论使用通用设计原则设计用户界面的注意事项,以及使您的界面可访问应采取的特殊注意事项。

In the following sections, I will discuss considerations for designing user interfaces using the principles of universal design as well as the special considerations you should take to make your interface accessible.

由于本书主要介绍 Unity 中的 UI 开发(它主要是一个视频游戏引擎),因此我将描述的大多数示例和用例都与视频游戏相关。

Since this book is on the development of UI within Unity, which is primarily a video game engine, the majority of the examples and use cases I will describe will be related to video games.

通用设计原则

Universal design principles

1997 年,北卡罗来纳州立大学的一个工作组制定了通用设计的 7 项原则。这些原则旨在帮助指导产品设计,以便它们能够具有普遍适用性。这些原则是根据不同类型的设计正式定义的,重点是架构。我将根据每个原则在数字用户界面中的应用对其进行解释,然后提供每个原则如何在视频游戏界面设计中使用的例子。如果您想查看原则列表及其具体指南,可以访问https://universaldesign.ie/what-is-universal-design/the-7-principles/

In 1997, a working group at North Carolina State University developed the 7 Principles of Universal Design. These principles are meant to help guide the design of products so that they can be universally usable. These principles are formally defined in terms of different types of designs with an emphasis on architecture. I will paraphrase each principle in terms of its application to digital user interfaces and then provide examples of how each principle can be used in video game interface design. If you’d like to view the list of principles and their specific guidelines, you can visit https://universaldesign.ie/what-is-universal-design/the-7-principles/.

许多原则是重叠的,我将提供的一些示例可能适用于多项原则。

Many of these principles overlap and some of the examples I will provide could apply to multiple principles.

公平使用

Equitable use

公平的使用原则指出,设计应该同样可用,对所有人都有吸引力。如果不可能为所有人提供相同的体验,那么他们应该拥有同等的体验。界面应该设计成对所有用户都有吸引力,不应该歧视或隔离用户。这是第一原则,因为它最终推动了所有其他原则。

The equitable use principle states that a design should be equally usable and appealing to all. If an identical experience is not possible for all, they should have an equivalent experience. An interface should be designed to appeal to all users and should not stigmatize or segregate users. This is the first principle because it ultimately drives all the other principles.

例如,界面中应使用高对比度的颜色。高对比度的界面不仅对所有用户都有吸引力,而且还可以帮助坐在阳光直射下的移动用户看清界面,避免让视力低下和色盲等视觉障碍用户感到不快。

For example, high-contrasting colors should be used in interfaces. High-contrasting interfaces are not only appealing to all users but they help mobile users sitting in direct sunlight see your interface and they avoid stigmatizing users with visual impairments such as low vision and color blindness.

请记住,通用设计的目标并不是明确地为无障碍设计(即为残障群体设计)。使用高对比度配色方案之类的东西对所有人都有好处,而不仅仅是那些有视力障碍的人。它也不是明确需要打开的设置,例如打开色盲模式,这就是无障碍设计。我将在本章后面讨论无障碍设计。

Remember that the goal of universal design is not explicitly about designing for accessibility (that is, designing for groups with disabilities). Utilizing something like a high-contrasting color scheme is beneficial to all, not just those with visual impairments. It is also not a setting that explicitly needs to be turned on, such as turning on a color blind mode, which would be designing for accessibility. I’ll discuss designing for accessibility later in this chapter.

如果您正在制作 PC 游戏,您不会希望隐藏信息以便只能通过鼠标访问。允许通过控制器上的方向键导航到信息或通过键盘按 Tab 键导航到信息将允许使用不同输入设备(由于个人偏好或视力障碍)的用户访问与鼠标用户相同的信息。

If you’re making a PC game, you don’t want to hide information so that it is only accessible by a mouse. Allowing information to be navigated to via the d-pad on a controller or tabbed to by a keyboard will allow users who are using different input devices (due to personal preference or vision impairments) to access the same information as a mouse user.

使用灵活

Flexibility in use

灵活使用原则规定,具有广泛偏好和能力的人应该设计应能适应用户的需求,并应为用户提供如何使用设计的选择。这一原则的关键是为用户提供与游戏互动的选择。

The flexibility in use principle states that people with a wide range of preferences and abilities should be accommodated by the design and people should be provided choice in how they use the design. The key to this principle is providing users with choices when it comes to interacting with your game.

如果您正在制作一款移动游戏,则可以提供左右手模式,即交换按钮在屏幕哪一侧的位置,就像我在游戏《Barkeology》中所做的那样,如下图所示。这样,玩家就可以轻松地与界面互动,而不会用惯用手阻碍游戏玩法。

If you’re making a mobile game, you can provide left and right-handed modes that swap which side of the screen the buttons are on, as I did in my game Barkeology, which is shown in the following figure. This allows players to easily interact with the interface without blocking the gameplay with their dominant hand.

图 4.1:iOS 版 Barkeology 中的左手模式与右手模式

图 4.1:iOS 版 Barkeology 中的左手模式与右手模式

Figure 4.1: Left-handed mode versus right-handed mode in Barkeology for iOS

Fantasy Life Online 让你在三个位置中选择一个放置“愤怒按钮”的位置——当玩家可以使用特殊攻击时,按钮就会出现。这允许玩家可以根据自己的游戏风格和握持手机的方式选择最舒服的位置。

Fantasy Life Online lets you choose between three locations for the placement of the “fury button” – a button that appears whenever a special attack is available to the player. This allows the player to pick a location that is most comfortable to them based on their playstyle and the way they hold their phone.

对于 PC 游戏,您可以允许玩家选择不同的输入设备。例如,您可以提供使用键盘和鼠标或控制器玩游戏的选项。您可以允许玩家随意映射键盘或控制器,或者为他们提供预定义的方案供他们选择。允许玩家选择控制器按钮方案也适用于游戏机。

For PC games, you can allow players to choose different input devices. For example, you can provide the option to play with a keyboard and mouse or a controller. You can allow players to map the keyboard or controller however they wish or give them predefined schemes to choose from. Allowing players to choose controller button schemes is also applicable to consoles.

如果您的游戏有文字,请允许用户更改文字的大小或显示速度

If your game has text, allow users to change the size or the speed at which it is presented to them.

还有许多其他例子,说明您可以为界面提供灵活性。在设计界面时,请务必考虑玩家可能具有的不同偏好,以便您可以相应地布局界面并映射输入。

There are many more examples of ways in which you can allow flexibility in your interfaces. When designing your interfaces, just be sure to consider the different preferences your players may have so that you can lay out your interface and map your inputs accordingly.

使用简单、直观

Simple and intuitive use

简单直观的使用原则指出,设计应该易于人们理解,无论他们过去的知识、经验、技能或语言水平如何。

The simple and intuitive use principle states that a design should be easy to understand for people, regardless of their past knowledge, experience, skill, or language level.

不要你的界面过于复杂。如果你必须解释它,那么它可能需要重新设计。从未玩过电子游戏的玩家应该能够像老手一样轻松理解你的界面。

Don’t make your interfaces overly complicated. If you have to explain it, it probably needs to be redesigned. Players who have never played a video game before should be able to understand your interface just as easily as a veteran.

将最重要的信息放在最显眼的位置,使它们最容易找到。不要将常用功能隐藏在多次点击按钮和菜单后面。如果您的菜单系统是嵌套的,并且特定菜单的访问频率高于其他菜单,请考虑将它们映射到热键或按钮。

Place the most important information in the most visible locations and make them the easiest to find. Don’t hide common functionality behind multiple button clicks and menus. If your menu system is nested and particular menus are accessed more frequently than others, consider mapping them to hotkeys or buttons.

向用户提供反馈,让他们知道他们何时与你的界面交互。如果你有屏幕按钮,可以通过点击或鼠标单击来交互,那么让它们向用户提供反馈,让他们知道他们何时突出显示或单击了它们

Provide feedback to the user to let them know when they are interacting with your interface. If you have on-screen buttons meant to be interacted with by a tap or mouse click, have them provide feedback to the user to let them know when they have highlighted them or clicked on them.

向用户提供提示,告知玩家界面的哪些部分可以交互。您可以通过设计看起来可按的按钮或使用动画或配色方案吸引用户对项目的注意来实现这一点。

Provide prompts to the user that signal to the player which parts of your interface are interactable. You can accomplish this by designing buttons that look physically pressable or by drawing attention to items with animations or color schemes.

重要的是要记住,您的用户会有不同的阅读水平,并且可能会说不同的语言。尽可能使用更多的图标隐喻来减少对文本的依赖。隐喻是其含义被广泛认可的符号。例如,大多数人会认识到下图中显示的按钮的含义:播放、暂停、菜单、设置、关闭/取消、确认、静音和保存:

It’s important to remember that your users will have various reading levels and may speak different languages. Reduce your dependency on text by using more icon metaphors when possible. Metaphors are symbols whose meanings are widely recognized. For example, most people will recognize the meaning of the buttons shown in the following figure as play, pause, menu, settings, close/cancel, confirm, mute, and save:

图 4.2:界面隐喻的示例

图 4.2:界面隐喻的示例

Figure 4.2: Examples of interface metaphors

用文字伴随图标隐喻,以便用户有多种方式来感知特定图标的含义。

Accompany the icon metaphors with text so that users have multiple ways of perceiving what specific icons mean.

完全删除游戏 UI 中的所有文本并用图标和图像替换并不总是可行的。因此,将游戏翻译成多种语言可以提高 UI 的普遍可感知性。在第 11 章中,我将讨论在构建 UI 时可以做的事情,以使翻译过程更加顺利,并介绍创建 UI翻译系统的示例。

It’s not always possible to completely remove all text from your game’s UI and replace it with icons and imagery. So, translating your game into multiple languages could increase your UI’s universal perceptibility. In Chapter 11, I will discuss things you can do while building your UI to make the translation process go more smoothly, as well as cover an example of creating a UI translation system.

可感知的信息

Perceptible information

可感知信息原则指出,无论用户所处的环境或感知能力如何,信息都应以可感知的方式传达给用户。在考虑这一原则时,您需要考虑以其他方式传达信息可以传达的内容以及如何在多种情况下清晰地传达。请记住,界面是玩家感知和与游戏互动的镜头。因此,他们必须能够理解它。

The perceptible information principle states that information should be conveyed to users in a perceptible way regardless of the environment they are in or their sensory abilities. When considering this principle, you want to think of alternate ways in which information can be conveyed and how you can clearly convey it in multiple scenarios. Remember that the interface is the lens through which the player perceives and interacts with your game. So, they must be able to understand it.

UI 的颜色应该与背景相比更加突出,但也不能太突出,以免造成眼睛疲劳。游戏没有特定的配色方案,但一般来说,分割互补配色方案最适合减少眼睛疲劳,同时还能产生足够的对比度,使物品易于区分。

The colors of the UI should stand out compared to the background, but it should also not stand out so much that it causes eye strain. There is no specific color scheme you have to use for your game, but as a general rule, split complementary color schemes are the best for reducing eye strain while also producing enough contrast to make items distinguishable.

高对比度的文本在不同光线下更容易被看到。确保文本大小合适,以便用户清晰可见,并允许根据用户的偏好调整文本大小。此外,选择清晰易读的字体

High-contrast text will make it easier to see in different lighting. Make sure the text is appropriately sized so that it’s visible and allow the text to be resized to the user’s preference. Also, choose fonts that are clear and easy to read.

画外音不仅仅适用于对话!您还可以为菜单提供画外音。例如,《孤岛惊魂 6》会自动用语音读出开始屏幕上的各种菜单选项,以便有视力障碍的人仍能感知屏幕上的项目。此选项默认开启,不需要用户与菜单交互即可访问。

Voiceover isn’t just for dialogue! You can provide voiceover for your menus as well. Far Cry 6, for example, has an automated voice read out the various menu options on the start screen to allow those with visual impairments to still perceive the items on the screen. This option is turned on by default and does not require users to interact with a menu to access it.

如果您正在制作主机游戏,请考虑玩家会使用不同类型的电视,这些电视的分辨率和亮度设置也不同。启动 PlayStation 或 Xbox 游戏时,您可能会看到提示,提示您调整亮度直到图像可见,或者提示您移动方框的角直到它们到达屏幕边缘。这些提示是确保无论玩家使用哪种电视,游戏都能正确显示的安全措施。

If you’re making a console game, consider the fact that your players will have different types of TVs with different resolutions and brightness settings. Upon starting a PlayStation or Xbox game, you may have seen a prompt to adjust the brightness until an image was visible or maybe saw a prompt to move the corners of a box until they reached the edge of your screen. These prompts are safeguards to ensure that the game appears correctly to players regardless of their TV.

容错率

Tolerance of error

容错原则规定,应尽量减少与设计交互的不良后果。我们都曾不小心保存或删除过保存文件在我们的生活中,我们可能会无意间按错按钮或误读指令。容错原则旨在最大限度地减少此类事件的发生。

The tolerance of error principle states that adverse consequences of interacting with the design should be minimized. We’ve all accidentally saved over or deleted a save file at one point in our life without meaning to because we hit the wrong button or misread the directions. The tolerance of error principle is meant to minimize these types of occurrences.

精心设计的界面不一定方便或易于使用。人们喜欢快速点击或轻触浏览内容。有时,我们想让这变得更难对他们来说。为不便而设计的概念包括使界面变得不那么方便或更难交互。这似乎是一种违反直觉的设计,但如果让你删除你不小心删除的最后一个保存文件的界面不那么方便,也许你的保存文件仍然会留在我们身边。

A well-designed interface doesn’t necessarily have to be convenient or easy to use. People love to click or tap quickly through things. Sometimes, we want to make that more difficult for them. The concept of designing for inconvenience involves making interfaces less convenient or more difficult to interact with. This may seem like a counterintuitive design, but if the interface that lets you delete that last save file you accidentally deleted were less convenient, perhaps your save file would still be with us.

要求用户仔细确认删除的警告弹出窗口、切换确认按钮的位置(这样用户就不会快速点击)、在点击之间添加计时器以及要求按住,这些都是我们可以让我们的界面稍微不太方便用户交互的方式,当与界面交互的后果可能是有害的时。

Warning popups that ask users to double confirm a deletion, switching the location of affirmation buttons so that users don’t click quickly through things, adding timers between clicks, and requiring press and hold are all ways that we can make our interfaces slightly less convenient for the user to interact with when the consequences of interacting with it may be detrimental.

低体力投入

Low physical effort

低体力原则规定,设计应使用舒适,并应尽量减少疲劳和不适。该原则的指导方针之一是尽量减少重复操作。这​​在视频游戏中似乎是不可能的,因为视频游戏往往需要大量重复操作,但以减少点击和鼠标拖动的方式组织界面可以提高UI 的质量。

The low physical effort principle states that the design should be comfortable to use and fatigue and discomfort should be minimized. One of the guidelines for this principle states to minimize repetitive actions. This may seem impossible to do in video games, which, let’s face it, tend to require a lot of repetitive actions, but organizing your interfaces in ways that reduce clicks and mouse drags can improve the quality of your UI.

减少界面操作体力消耗的一种方法是将屏幕上的类似操作分组。不要让用户在屏幕上来回切换。如果用户在单个视图菜单中完成所有操作会更方便,则不要要求他们跳转到多个菜单。您可以通过将操作分配给热键或创建快捷方式来减少鼠标使用/拖动。此外,允许用户使用箭头键或控制器按钮(而不仅仅是鼠标)浏览菜单可能会让某些用户感到更舒服。

One way to reduce physical effort with your interface is to group similar actions on the screen. Don’t make users bounce back and forth on the screen. Don’t require users to jump to multiple menus when it would be easier for them to do something all in a single view menu. You can reduce mouse usage/drag by assigning actions to hotkeys or creating shortcuts. Also, allowing users to navigate through menus with arrow keys or controller buttons rather than just the mouse may be more comfortable for some users.

在 VR 中设计 UI 时,与传统 2D 屏幕上的游戏相比,与界面交互所需的体力投入呈指数级增长。将相似的项目分组并允许用户使用控制器而不是指向来浏览菜单,将使与界面的交互更加舒适。

When designing UI in VR, the amount of physical effort needed to interact with your interface jumps exponentially compared to a game on a traditional 2D screen. Grouping similar items and allowing users to navigate menus with the controller rather than pointing will make interacting with your interface much more comfortable.

震动功能是提供界面和游戏反馈的绝佳方式,但对于某些玩家来说,震动功能可能不太舒服,据称与手臂震动综合症有关。如果您添加此功能,请务必允许用户将其关闭。

The rumble feature can be a great way to provide feedback for your interface and gameplay, but it can also be considered uncomfortable to some players and has been purportedly linked to hand-arm vibration syndrome. If you include it, be sure to allow users to turn it off.

令人沮丧的主机游戏的交互方式是将信息输入表单。如果玩家使用屏幕键盘输入长文本字符串,则必须快速导航到每个字母,这非常繁琐。考虑允许玩家在手机或电脑上输入这些长字符串,然后将数据发送到游戏,而不是在主机上。

One frustrating interaction on a console game is entering information into a form. If you have players enter long text strings with an on-screen keyboard, having to navigate to each letter gets very tedious, very fast. Consider the possibility of allowing players to enter these long strings on their phone or computer and send the data to the game, instead of on the console.

尺寸和空间,方便接近和使用

Size and space for approach and use

大小和空间的接近和使用原则指出界面应该允许用户无论他们的行动能力、体型、姿势或位置如何,都要与他们互动。这一原则与之前的低体力原则紧密相关,因为你希望他们不仅能够身体上与您的界面进行交互,而且操作起来也很舒服。

The size and space for approach and use principle states that interfaces should allow users to interact with them regardless of their mobility, size, posture, or position. This principle ties closely to the previous principle of low physical effort in that you want them to not only be able to physically interact with your interface but also do so comfortably.

在确定游戏的键盘布局时,请确保玩家不必以对于手小的人来说不可能或不舒服的方式伸展双手。如果控制器上有两个经常一起使用的按钮,则要确保按钮组合是可行的。例如,您不想要求他们同时按住 Xbox 控制器上的 X 按钮和 B 按钮

When determining the keyboard layout for your game, make sure that the player does not have to stretch their hands in ways that are either impossible or uncomfortable for those with smaller hands. If you have two buttons on a controller that are frequently used together, you want to make sure that the button combination is doable. For example, you do not want to ask them to hold down both the X button and the B button on the Xbox controller at the same time.

允许多种类型的输入设备是这一原则的关键,因为它将允许人们使用为其特定尺寸和移动性设计的输入设备。例如,对于 PC 游戏,有些人可能会发现与控制器交互比与键盘交互更容易。

Allowing for multiple types of input devices is key to this principle as it will allow people to use input devices designed for their specific size and mobility. For example, with a PC game, some people may find interacting with a controller easier than a keyboard.

此外,允许调整输入灵敏度可以帮助那些有不同行动障碍的人。例如,我有手部疼痛和颤抖的问题。当我可以调整控制器和鼠标的灵敏度时,这让我更容易与游戏互动,既不影响游戏玩法,又可以减轻我这样做时所经历的痛苦。

Additionally, allowing for the sensitivity of your inputs to be adjusted can help those with different mobility issues. For example, I have problems with hand pain and tremors. When I can adjust the sensitivity on controllers and mice, this makes it easier for me to interact with the game in a way that doesn’t affect gameplay and can reduce the pain I experience doing so.

设计 VR 界面时,不要将 UI 项目放置得太高或太远离玩家的触及范围。这可能会使体型较小的玩家、坐着的玩家或行动不便的玩家无法与UI 进行交互。

When designing a VR interface, don’t put UI items too high or too far away from the player’s reach. This could make it impossible for smaller players, players sitting down, or players with mobility issues to interact with your UI.

在第 2 章中,我们讨论了拇指区域。这是屏幕上玩家在握着手机时拇指可以轻松触及的位置,也是大部分应放置可交互的 UI。我们还希望确保按钮在移动设备屏幕上尽可能大。不希望按钮太小且太靠近,以免手较大的人无法与它们交互。

In Chapter 2, we discussed the thumb zone. This is a location of the screen that can be easily reached by the player’s thumb while they’re holding their phone and where most of your interactable UI should be placed. We also want to make sure that buttons are as big as possible on mobile screens. You don’t want the buttons to be so small and so close together that those with larger hands would not be able to interact with them.

现在我们已经回顾了通用设计的各种原则,让我们看看如何专门为那些有缺陷和残疾的人进行设计。

Now that we’ve reviewed the various principles of universal design, let’s look at how we could design specifically for those with impairments and disabilities.

无障碍设计

Accessibility design

回想一下,通用设计涉及设计可供所有人普遍使用的 UI。另一方面,设计则涉及在设计时考虑特定的障碍和残障。在本节中,我将讨论几种非常具体的障碍和残障类型,以及如何设计 UI,以便让患有这些障碍和残障的人能够访问。其中一些示例将与我在通用设计部分讨论的示例重叠。

Recall that universal design involves designing UI that is universally usable for all people. Accessibility design, on the other hand, involves designing with specific impairments and disabilities in mind. In this section, I will discuss a few very specific types of impairments and disabilities and how you can design your UI in such a way that it is accessible to individuals with these impairments and disabilities. A few of these examples will overlap with the ones that I discussed in the universal design section.

想象

Vision

在设计时您的界面,您应该考虑不同类型的视觉障碍和残疾,包括(但不限于)色盲、视力低下和失明。

When designing your interfaces, you should consider different types of visual impairments and disabilities, including (but not limited to) color blindness, low vision, and blindness.

重要信息绝不能仅通过颜色来传达,而应始终包含传达该信息的另一种方式。例如,假设一个游戏中的数字为负数时为红色,为正数时为绿色。该数字也可以有负号和正号符号来表示符号。如果数字代表某种变化,也许它可以在正数时向上飞,在负数时向下飞。这将确保色盲者仍然能够看到这些重要信息

Essential information should never be conveyed by color alone and you should always include another way of conveying that information. For example, let’s say a game has a number that is red when negative and green when positive. That number could also have negative and positive symbols to indicate the sign. If the number represents some change, maybe it can fly up when positive and fly down with negative. This will make sure those who have color blindness will still be able to see these important pieces of information.

尽可能避免使用色盲人士无法辨别的颜色组合;如果无法做到,请使用其他指示物来区分物品。例如,如果您有一款使用颜色来指示可匹配的棋子的三消游戏,请在棋子上添加符号,以便更容易区分它们。

Whenever possible, you should avoid color combinations that will be indistinguishable for those who are color blind; if it’s not possible, use other indicators to make items distinct. For example, if you have a match three game that uses colors to indicate matchable pieces, also include symbols on the pieces to make them easier to distinguish.

下列网站提供了关于为色盲设计可访问 UI 的非常好的信息:

The following websites offer very good information on designing accessible UI for color blindness:

如本章前面所述,您应该确保您的文本和其他 UI 元素具有非常高的对比度。这样,色盲和视力低下的人就可以更清楚地感知您的 UI。以下网站是检查两种颜色对比度的绝佳资源https ://webaim.org/resources/contrastchecker/ 。

As stated earlier in this chapter, you should make sure that your text and other UI elements have very high contrast. This makes it easier for those with color blindness and those with low vision to be able to clearly perceive your UI. The following website is an excellent resource for being able to check the contrast between two colors: https://webaim.org/resources/contrastchecker/.

虽然上述网站显示了颜色是否满足网络法规要求的对比度,但该信息可以很好地转化为视频游戏。

While the preceding website shows if colors meet a contrast ratio required by web regulations, the information translates well to video games.

如果您的重要信息仅以弹出窗口的形式暂时显示在屏幕上,请确保它出现在玩家的视线范围内。将其放置在视线之外可能会让周边视力较差的人难以看到。更好的办法是,让玩家选择重要信息在屏幕上弹出的位置。

If you have important information that only appears temporarily on the screen in the form of a popup, make sure it appears in the player’s line of sight. Placing it outside of the line of sight could be difficult for those with low peripheral vision to see. Better yet, allow the player to choose where on the screen the important information will pop up.

请记住,根据通用设计原则,选择非常重要。允许玩家更改尽可能多的视觉元素的设置。如果您的游戏使用十字准线或光标,请允许玩家更改十字准线或光标的外观。允许玩家更改界面或文本的大小。允许玩家将界面的字体更改为字距或间距更大或花哨的字体

Remember, according to the principles of universal design, choice is very important. Allow players to change settings for as many of the visual elements as possible. If your game uses a crosshair or cursor, allow the player to change the appearance of the crosshair or cursor. Allow your players to change the size of the interface or text. Allow players to change the font of the interface to one that has higher kerning or spacing or is less frilly.

以非纯视觉的方式显示信息也很重要。例如,假设您的 UI 变成红色,表示玩家生命​​值较低,屏幕上显示红色生命值计。您还可以添加蜂鸣声和控制器振动来指示生命值较低。这将有助于视力低下和色盲的人能够感知生命值较低状态。

It’s also important to indicate information in ways that are not purely visible. For example, suppose your UI turns red to indicate your player has low health and you have a red health meter on the screen. You can also include a beeping sound and a vibration of the controller to indicate low health. This will help those with both low vision and color blindness be able to perceive the low health status.

如果您有输入框,您可以使用语音作为输入文本的方式,而不仅仅是使用视觉键盘进行输入。

If you have input boxes, you can use speech as a way to input text, rather than just a visual keyboard for input.

请记住,你可以还可以使用描述轨道来描述游戏的所有用户界面元素,从而让视力低下和失明的人仍然能够感知屏幕上的内容。

Remember, you can also use a description track to describe all of the user interface elements of your game, thus allowing those with low vision and blindness to still be able to perceive what is on the screen.

听力和言语

Hearing and speech

有多种方法可以让听力障碍人士访问你的界面,残障人士的言语障碍。本质上,您不希望任何重要信息仅通过声音传达,也不希望任何输入都需要语音。此外,如果您的 UI 产生任何类型的噪音,请将重叠噪音降至最低。

There are multiple ways in which you can make your interface accessible to those with hearing and speech impairments in disabilities. Essentially, you do not want any important information to be conveyed through sound alone and you do not want to require speech for any of your inputs. Additionally, if your UI makes any type of noise, keep overlapping noises to a minimum.

在我之前描述的低生命值示例中,除了哔哔声之外,还有多种方式可以让玩家意识到低生命值。如果您的 UI 包含语音输入,请确保它不是唯一的输入形式,并允许用户通过其他方式输入信息。

In the low-health example I described previously, there were multiple ways other than just the beeping sound in which the player was made aware of the low health. If your UI includes speech input, make sure that it is not the only form of input and allows users to input information through other means.

虽然看起来界面设计(包括所有对话的字幕)不需要设计为 UI 元素,但字幕的 UI 需要清晰易读。通常,您应该将它们放在某种框中,以便它们与背景形成鲜明对比。您还需要确保它们与游戏中的口语同步。

While it might not seem like an interface design, including subtitles for all spoken dialogue, would need to be designed as a UI element. Your UI for subtitles will need to be clear and readable. Usually, you should put them in some sort of box so that they easily contrast against the background. You also want to make sure that they sync up with the spoken speech in the game.

移动性

Mobility

在设计界面时,您需要确保它们适合各种行动能力水平的用户。让您的游戏适合各种行动能力水平的用户的最佳方式是包含为输入设备、输入映射和配置提供尽可能多的选项。允许玩家自行选择如何与界面交互可以确保界面满足他们特定的移动需求。

When designing your interfaces, you want to make sure they are accessible for people with all levels of mobility. The best way to make your game accessible to mobility levels is to include as many options as possible for input devices, input mappings, and configurations. Allowing the player to choose for themselves how they interact with your interface might ensure that it fits their particular mobility needs.

您希望您的控件尽可能简单,并且需要的按钮尽可能少。如果您的控件特别复杂,请提供简化控制方案的替代方案。

You want your controls to be as simple as possible and require as few buttons as possible. If your controls are particularly complicated, provide alternatives that simplify the control scheme.

此外,不要要求玩家在游戏中使用与游戏其他部分不同的输入方式。例如,如果游戏的大部分内容都是通过键盘控制的,则不要要求玩家使用鼠标与开始屏幕进行交互。允许玩家使用游戏的各个方面均使用相同的输入设备。

Additionally, don’t require the player to use an input for a small part of the game that is different than the rest of the game. For example, if most of the game is controlled by a keyboard, don’t require the start screen to be interacted with by a mouse. Allow the player to use the same input device for all aspects of the game.

认知和情感

Cognitive and emotional

在设计用户界面时,考虑玩家的认知和情感状态以及他们的语言技能非常重要

It’s important to consider the cognitive and emotional states of your players as well as their language skills when designing your user interface.

不要假设确保玩家能够阅读您创建 UI 时使用的语言,或者能够快速阅读。如果您的游戏包含文本,请使用其他上下文元素和图像来传达文本的意图。如果您的 UI 中显示了一些文本,然后又消失了,请允许玩家选择文本消失的时间,而不是让文本按照自己的速度消失。

Don’t assume that your player can read the language in which you have created your UI or that they can read quickly. If your game contains text, use other contextual elements and images to convey the text’s intent. If you have text that displays in your UI and then goes away, allow players the ability to choose when it goes away rather than having it do so at its own speed.

使用 UI 提醒玩家重要的游戏元素和有关如何与界面交互的重要信息。通过在 UI 中突出显示控件来提醒玩家控件是什么。使用 UI 清楚地指示交互元素和目标。

Use your UI to remind players of important gameplay elements and important information about how to interact with your interface. Remind your players of what the controls are by prominently displaying them in your UI. Use your UI to clearly indicate interactive elements and objectives.

不要在用户界面中放置闪烁的图像或重复的图案,因为这可能会引发易患癫痫的人的症状。

Don’t place flickering images or repetitive patterns in your UI as this could be triggering to individuals prone to seizures.

虽然这可能与你的游戏玩法而非用户界面更相关,但我想指出的是,在具有特别触发内容的游戏中设置内容警告有助于让用户为可能引起困扰的事情做好准备。例如,现在许多游戏如果包含令人不安的内容(例如 Doki Doki Literature Club),都会附带内容警告。

While this is probably more related to your gameplay than your user interface, I would like to point out that content warnings in your games with particularly triggering content can be helpful to prepare your user for things that may cause distress. For example, many games now come with a content warning if they have upsetting content (such as Doki Doki Literature Club).

其他资源

Additional resources

关于用户界面的通用设计和可访问性设计方面,我还有很多话要说,但遗憾的是,这不是一本关于 UI 设计的书,而是关于 UI 开发的书。我希望本章至少能让您了解考虑这些元素的重要性,以及在用户界面开发之初考虑它们的重要性。

There is so much more I could say about the universal design and accessibility design aspects of user interfaces, but alas, this is not a book about designing UI it is about the development of UI. I hope this chapter at least gave you an insight into how important it is to consider these elements and how important it is to consider them at the beginning of your user interface development.

如果你想了解更多关于无障碍设计的知识,我强烈建议你查看以下网站提供的信息:https ://gameaccessibilityguidelines.com/full-list/ 。它提供了与视频游戏可访问性相关的最佳实践的详尽列表。此外,它还提供了多个最佳实践示例供您参考。

If you’d like to learn more about accessibility design, I highly recommend that you review the information provided at the following website: https://gameaccessibilityguidelines.com/full-list/. It provides an extensive list of best practices related to accessibility for video games. Additionally, it provides multiple examples of best practices that you can use for reference.

您还可以访问https://caniplaythat.com/,该网站提供有关游戏行业可访问性的信息。

You can also visit https://caniplaythat.com/, a website that provides information about accessibility in the gaming industry.

概括

Summary

在本章中,我们讨论了设计 UI 时的一些关键考虑因素,以便让其尽可能地被普遍感知。我们讨论了通用设计的原则,并回顾了一些可以在设计中实施的考虑因素,以提高其可访问性。

In this chapter, we discussed some key considerations for designing your UI so that it will be as universally perceivable as possible. We discussed the principles of universal design and reviewed some considerations you can implement in your designs to improve their accessibility.

在下一章中,我们将抛开设计讨论,开始研究如何实现 UI。我们将回顾 Unity 中可用的不同界面系统,以便您可以开始运用这些设计知识!

In the next chapter, we will leave the design discussion behind and start looking at how to implement UI. We’ll review the different interface systems available in Unity so that you can start putting some of this design knowledge to use!

5

5

Unity 中的用户界面和输入系统

User Interface and Input Systems in Unity

现在我们已经讨论了开发用户界面的各种设计注意事项,我们可以开始讨论如何在 Unity 中实现它们。Unity 提供了各种用于创建 UI 的系统。它有现成的系统允许您创建将在您的游戏中显示的 UI,或仅在编辑器中显示的 UI。此外,它还提供了多个用于接收来自玩家的输入的系统。

Now that we’ve discussed various design considerations for developing user interfaces, we can start discussing how to implement them within Unity. Unity provides various systems for creating UI. It has systems in place that allow you to create a UI that will be displayed in your game, or UI that will be displayed only in the Editor. Additionally, it provides multiple systems for receiving input from the player.

在撰写本文时,其中两个系统仍在积极开发中,默认情况下未包含在 Unity 中。本书将主要关注已完成系统的开发,但由于 Unity 确实打算在某个时候将这些系统作为标准功能,因此,如果因为它们仍处于预览阶段而不讨论它们,那我就太失职了。

At the time of writing, two of these systems are still in active development and do not come packaged in Unity by default. This book will primarily focus on development with the systems that are complete, but since Unity does intend to make these systems standard features at some point, I would be remiss not to discuss them just because they are still in preview.

在本章中,我将讨论以下主题:

In this chapter, I will discuss the following topics:

  • 识别 UI Toolkit、Unity UI和 IMGUI
  • Identifying UI Toolkit, Unity UI, and IMGUI
  • 在三个UI 系统之间进行选择
  • Choosing between the three UI systems
  • 识别输入管理器和输入系统
  • Identifying the Input Manager and the Input System
  • 在输入管理器和输入系统之间进行选择
  • Choosing between the Input Manager and the Input System

让我们首先了解一下 Unity 中的三个 UI 系统

Let’s start by looking at the three UI systems within Unity.

三个 UI 系统

The three UI systems

Unity 有三个系统可以用于构建 UI。您选择哪种方式取决于您的 UI 将在何处显示、您要完成的任务、您是否正在处理现有项目以及您对编码的熟悉程度。

Unity has three systems that can be used to build UI. Which you choose will depend on where your UI will be displayed, what you are trying to accomplish, whether you’re working on a pre-existing project, and how comfortable you are with coding.

您构建的 UI 可以是游戏内的,也可以是编辑器内的。游戏内的 UI 是玩家可以访问的 UI。编辑器内的 UI 是在 Unity 编辑器内显示的并有助于开发的 UI。

The UI you build can either be in-game or in-Editor. In-game UI is the UI that can be accessed by your players. In-Editor UI is UI that displays within the Unity Editor and assists with development.

如果您想为游戏或应用程序构建 UI,您可以选择 Unity UI (uGUI) 系统或 UI 工具包。如果您想构建在 Unity 编辑器中显示的 UI,您可以使用 UI工具包或 IMGUI。以下维恩图总结了不同 UI 系统的用途

If you want to build UI for your game or application, you can choose between the Unity UI (uGUI) system or the UI Toolkit. If you want to build UI that appears in your Unity Editor, you can use either the UI Toolkit or IMGUI. The following Venn diagram summarizes the uses of the different UI systems:

图 5.1:游戏内和编辑器内 UI 的比较

图 5.1:游戏内和编辑器内 UI 的比较

Figure 5.1: A comparison of in-game and in-Editor UI

看看图 5.1您可能会想,“好吧,UI Toolkit 适用于一切!我只需学习它,就可以完成我的 UI 学习之旅!这是一个简单的选择!”不幸的是,事情并没有那么简单。让我们更深入地了解不同的系统,以便您可以决定哪个适合您。

Looking at Figure 5.1, you may be thinking, “Well, UI Toolkit works for everything! I’m just going to learn that and be done with my UI learning journey! That was an easy choice!” Well, unfortunately, it’s not that simple. Let’s look at the different systems a little more in-depth so that you can decide which is right for you.

Unity UI(或 uGUI)

Unity UI (or uGUI)

Unity UI系统(也称为uGUI )是 Unity内置的开箱即用系统,无需任何额外下载。它基于 GameObject 和 Component,包含多种类型的 UI可供选择的元素。当谈到为游戏或应用程序开发 UI 时,这是最强大和最稳定的选择。由于这是 Unity 中唯一一个非预览模式的游戏内 UI 构建系统,因此本文的大部分内容将重点介绍如何使用该系统开发 UI。

The Unity UI system, also known as uGUI, is the system that is built into Unity out of the box and doesn’t require any additional downloads. It is GameObject and Component-based and includes multiple types of UI elements to choose from. When it comes to developing UI for a game or application, this is the most robust and stable option. Since this is the only system for building in-game UI that is not in preview mode and is included within Unity, the majority of this text will focus on how to develop UI using this system.

图形用户界面

IMGUI

IMGUI (即时模式 GUI )系统是一种基于代码的 GUI 系统,用于编辑器内创建界面。其主要功能是协助程序员进行开发,不建议用于由于性能问题,游戏内 UI 的开发暂停。由于此系统并非旨在在游戏中使用,并且本书主要关注游戏的 UI 开发,我不会花大量时间来介绍它,但我将在第 19 章讨论它的一些基本功能和用法。

The IMGUI, or Immediate Mode GUI, system is a code-based GUI system used to make interfaces within the editor. Its primary function is to assist programmers with development, and it is not recommended for the development of in-game UI due to performance issues. Due to the fact that this system is not intended to be used in-game and this book will primarily concern itself with UI development for games, I won’t spend a significant amount of time covering it, but I will discuss some of its basic functionality and usage in Chapter 19.

UI 工具包

UI Toolkit

UI Toolkit是正在积极开发的新 UI 系统。默认情况下,它不包含在引擎中,必须与包管理器。此外,预览包,这意味着您必须选择在 Unity 提供的可用包列表中查看它。Unity 确实计划最终用 UI Toolkit 替换 uGUI 和 IMGUI。UI Toolkit 采用传统的 Web 开发概念设计,其结构与基于 GameObject 的 uGUI 完全不同。在第 18 章中,我将介绍如何下载 UI Toolkit 包以及如何使用它。

UI Toolkit is a new UI system that is in active development. It is not included within the engine by default and must be downloaded with the Package Manager. Additionally, it is a preview package, which means you have to elect to even see it in the list of available packages provided by Unity. Unity does plan on replacing both uGUI and IMGUI with UI Toolkit eventually. UI Toolkit is being designed with traditional web-development concepts and is structured entirely differently than the GameObject-based uGUI. In Chapter 18, I will cover how to download the UI Toolkit package and how to work with it.

在 UI 系统之间进行选择

Choosing between the UI systems

您选择哪种系统使用将取决于一些因素。如前所述,您是为编辑器还是游戏制作 UI 将决定您选择哪个系统。如果您正在为游戏制作 UI,则可以使用 Unity UI 或 UI Toolkit。如果您正在为编辑器制作 UI,则可以使用 IMGUI 或UI Toolkit。

Which system you choose to use is going to depend on a few things. As discussed earlier, whether you are making UI for the Editor or a game will determine which system you choose. If you’re working on UI for a game, you can use Unity UI or UI Toolkit. If you’re working on UI for the Editor, you can use IMGUI or UI Toolkit.

UI Toolkit 是一个尚未完全实现的新系统,因此如果你正在开发一款已有 UI 的游戏,你可能不会使用 UI Toolkit。它也可能不具备所有功能您希望与之合作。由于 UI Toolkit 正在开发中,因此不能保证其稳定性,并且在开发过程中对其进行更新可能会对您的项目产生不利影响。

UI Toolkit is a new system that is not fully implemented, so if you are working on a pre-existing game with UI already in place, you probably won’t be using UI Toolkit. It also may not have all the features you are looking to work with. Because UI Toolkit is in development, it is not guaranteed to be stable, and updating it mid-development may adversely affect your project.

您对编码的熟悉程度也会影响您的决定。一般来说,使用 Unity UI 所需的编码工作量较少比 IMGUI 更简单,可能比 UI Toolkit 更熟悉,因为它是基于 GameObject 的。但是,如果您熟悉基于 Web 的 UI 创建,那么 UI Toolkit对您来说可能非常熟悉。

Your comfort with coding could also drive your decision. In general, the coding required to use Unity UI is less intensive than IMGUI and may be more familiar to you than UI Toolkit, since it is GameObject-based. However, if you are familiar with web-based UI creation, UI Toolkit may seem really familiar to you.

如果您正在考虑使用 UI 工具包,我建议您在决定使用哪个系统之前查看本书中的示例以及以下 Unity 文档页面https://docs.unity3d.com/Manual/UI-system-compare.xhtml

If you are considering using the UI Toolkit, I recommend reviewing the examples in this book, as well as the following Unity documentation page, before you make your decision on which system to use: https://docs.unity3d.com/Manual/UI-system-compare.xhtml.

现在我们已经回顾了 Unity 提供的三个 UI 系统,我们可以回顾一下两个输入系统。

Now that we’ve reviewed the three UI systems provided by Unity, we can review the two input systems.

两种输入系统

The two input systems

根据第 1 章的定义,UI代表用户界面,涵盖用户和游戏相互传达信息。在讨论三个 UI 时系统,我们讨论了游戏与用户沟通的三种方式——具体来说,是通过在输出设备(即屏幕)上使用 GUI。但是,如果用户想要与游戏沟通,用户必须有一些可以提供输入的手段。然后游戏需要处理该输入。

As defined in Chapter 1, UI stands for user interface and encompasses all mechanisms by which the user and the game convey information to each other. When discussing the three UI systems, we talked about three ways in which the game communicates with the user – specifically through the use of GUI on an output device (i.e., a screen). However, if the user wants to communicate with the game, the user will have to have some means through which they can provide input. The game will then need to process that input.

Unity 有两种核心方式可以处理输入。输入管理器输入系统。就像除了各种因素会决定您使用哪种 UI 系统之外,还有各种因素可以帮助您确定要使用哪种输入系统。这两种系统都允许您轻松地处理多种类型的输入,就好像它们是同一件事一样。例如,每个系统都允许您处理键盘空格键和 Xbox 控制器 A 按钮,就好像它们是同一种类型的输入一样。它们如何做到这一点将在后面的章节中更详细地讨论,但现在,我们只看一下两者之间的一般差异。

There are two core ways in which Unity can handle input. The Input Manager or the Input System. Just as there are varying factors that would determine which UI system you may use, there are also varying factors that can help you determine which input system to use. Both systems allow you to easily process multiple types of input as if they are the same thing. For example, each system will let you process a keyboard space bar and an Xbox controller A button as if they are the same type of input. How they do that will be discussed more thoroughly in later chapters, but for now, let’s just look at the general differences between the two.

输入管理器

The Input Manager

输入管理器是输入系统,Unity 默认自带,无需下载任何额外软件包。无需配置任何设置,您可以轻松接受来自键盘、鼠标、操纵杆或触摸屏等。它通过提供预定义的输入轴来实现这一点,这些输入轴本质上指定了绑定到它们的关键字和按钮。我们将在第 8 章中更详细地回顾它是如何运作的

The Input Manager is the input system that comes with Unity by default and does not require any additional package downloads. Without having to configure any settings, you can easily accept input from things such as a keyboard, mouse, joystick, or touchscreen. It achieves this by providing pre-defined input axes that essentially specify keywords and buttons that bind to them. We will review how this functions more thoroughly in Chapter 8.

新的输入系统

The new Input System

输入系统(俗称新输入系统)是一个目前正在开发的软件包,正如 Unity其文档中指出,旨在比输入管理器更强大、更灵活、更可配置:https ://docs.unity3d.com/Packages/com.unity.inputsystem@1.3/manual/index.xhtml 。

The Input System (colloquially referred to as the new Input System) is a package that is currently in development and is, as Unity states in its documentation, intended to be more powerful, flexible, and configurable than the Input Manager: https://docs.unity3d.com/Packages/com.unity.inputsystem@1.3/manual/index.xhtml.

如果你一直在如果您的项目正在使用输入管理器,则可以将您的项目转换为使用新输入系统的项目。我们将在第 20 章中讨论如何实现输入系统。

If you have been working on a project that is using the Input Manager, it is possible to convert your project to one that uses the new Input System. We will discuss how to implement the Input System in Chapter 20.

在输入系统和新输入系统之间进行选择

Choosing between the Input System and the new Input System

首先我要说的是,选择哪种系统不一定有正确答案。理论上,你可以用任何一个系统处理你想要的任何类型的输入。你选择哪个系统主要取决于你的偏好、你的项目输入集的复杂程度,以及您正在为多个平台进行开发。

Let me start by saying there’s not necessarily a right answer on which system you choose. You can theoretically process any type of input you wish with either system. Which you choose will primarily be based on preference, how complicated your project’s set of inputs is, and whether you are developing for multiple platforms.

如果您不打算让播放器重新映射控件(如第 4 章所述)或不打算进行跨平台开发,那么使用输入管理器可能没问题,无需下载输入系统。但是,如果您想拥有超可配置的控制方案、接受来自各种设备的输入并接受复杂的输入操作,那么您可能会发现使用输入系统编写处理这些输入的代码比使用输入管理器更容易

If you are not planning on allowing your player to remap controls (as discussed in Chapter 4) or are not planning on cross-platform development, you are probably fine with using the Input Manager and don’t need to go through the process of downloading the Input System. However, if you want to have ultra-configurable control schemes, accept inputs from a variety of devices, and accept complicated input actions, you will likely find it easier to write the code that processes these inputs using the Input System than you would using the Input Manager.

由于输入管理器用于目前正在开发的许多项目,因此,如果我仅仅因为它不是新的热门而完全忽略它,那对您来说就太不公平了。此外,新的输入系统仍然很新,仍在积极开发中,因此可能会发生重大变化每次更新都会出现这种情况。但是,它确实使某些东西的构建变得更容易,并且在开发人员中越来越受欢迎。因此,我不会只选择其中一个系统来在本书中重点介绍。

Since the Input Manager is used in so many projects that are currently in development, I would be doing you a disservice to completely omit it just because it is not the new hot. Additionally, the new Input System is still new and still in active development, so it is subject to drastic changes with each update. However, it does make some things significantly easier to build and is gaining traction in popularity among developers. Therefore, I will not choose just one of these systems to focus on in this book.

概括

Summary

Unity 提供了多种方式,您可以通过三种 UI 系统向用户显示信息。选择哪种方式取决于您的需求以及您是为游戏还是编辑器开发 UI。本书将主要关注 uGUI,因为它是用于游戏内开发的最稳定的 UI 版本,并且已在 Unity 中提供,无需额外下载。但是,如何使用 IMGUI 开发编辑器 UI 以及如何使用 UI 工具包来同时使用编辑器 UI 和游戏内 UI 将在本书的后面章节中讨论。

Unity provides multiple ways in which you can display information to your user through the use of three UI systems. Which you choose depends on your needs and whether you are developing UI for a game or the Editor. This book will primarily focus on uGUI, since it is the most stable UI version used for in-game development and is provided within Unity, without additional downloads. However, how you can use IMGUI to develop Editor UI and the UI Toolkit to use both Editor UI and in-game UI will be discussed in the later chapters of this book.

Unity 还提供了多种处理用户输入的方法。虽然新的输入系统仍在开发中,并且不是 Unity 的默认功能,但我将确保为您提供足够的信息,以便在您的项目中使用它。

Unity also provides multiple ways in which you can process inputs from a user. While the new Input System is still in development and does not come with Unity by default, I will make sure to give you enough information to use it in your projects.

在下一章中,我们将开始使用 uGUI 系统开发 UI,通过探索 UI 画布、面板和布局。

In the next chapter, we will start developing UI using the uGUI system, by exploring UI Canvases, Panels, and layouts.

第 2 部分:Unity UI 基础知识

Part 2: Unity UI Basics

在本部分中,您将学习使用Unity UI ( uGUI ) 系统的基础知识。您将学习如何使用 Canvases 和 Panels 及其 Rect Transform 组件来布局 UI。您还将了解如何使用 Unity 的各种自动布局组件来帮助您更轻松地设计 UI。最后,您将了解如何为 uGUI 系统编写代码以及如何为您的 UI 编写交互程序。

In this part, you will learn the basics of working with the Unity UI (uGUI) system. You’ll learn how to lay out UIs with the use of Canvases and Panels and their Rect Transform component. You’ll also look at how you can use Unity’s various Automatic Layout components to help you design UI more easily. Lastly, you’ll look at how you can write code for the uGUI System and program interactions for your UI.

本部分包含以下章节:

This part has the following chapters:

6

6

画布、面板和基本布局

Canvases, Panels, and Basic Layouts

如上一章所述,本文的大部分内容将重点介绍 Unity UI 系统 uGUI。画布是使用 Unity UI 制作的所有 UI 的核心。每个 uGUI 元素都必须包含在画布中,才能在场景中渲染。它的工作原理类似于艺术家绘画的画布,但我们不是在画布上绘画,而是在画布上布置 UI 元素。因此,我们将从画布开始探索 uGUI 系统中提供的各种 UI 元素。

As discussed in the previous chapter, the majority of this text will focus on the Unity UI system, uGUI. Canvases are the core of all UI made with Unity UI. Every single uGUI element must be contained within a Canvas for it to render within a scene. It works similarly to a canvas on which an artist paints, but instead of painting on them, we lay out UI elements on them. So, we’ll start our exploration of the various UI elements provided in the uGUI system with Canvases.

画布的作用不仅是容纳所有 UI 元素,还决定元素的渲染方式和缩放方式。尽早设置可在多种分辨率和宽高比下缩放的 UI 非常重要,因为以后再尝试这样做会带来很多麻烦和额外的工作。因此,我们还将讨论如何确保我们的 UI 能够适当缩放。

Canvases serve the purpose of not only holding all the UI elements within them but also determining how the elements will render and how they will scale. It’s important to start focusing on setting up a UI that will scale at multiple resolutions and aspect ratios early on, as trying to do so later will cause a lot of headaches and extra work. Therefore, we will also discuss how to make sure our UI scales appropriately.

在本章中,我们将讨论以下主题:

In this chapter, we will discuss the following topics:

  • 创建 UI Canvas 并设置其属性
  • Creating UI Canvases and setting their properties
  • 创建 UI 面板并设置其属性
  • Creating UI Panels and setting their properties
  • 使用矩形工具和矩形变换组件
  • Using the Rect Tool and the Rect Transform component
  • 正确设置锚点和枢轴点
  • Properly setting anchor and pivot points
  • 如何创建和布局基本的HUD
  • How to create and lay out a basic HUD
  • 如何创建背景图像
  • How to create a background image
  • 如何设置基本的弹出菜单
  • How to set up a basic pop-up menu

技术要求

Technical requirements

您可以在这里找到本章的相关代码和资产文件

You can find the relevant codes and asset files of this chapter here:

https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2006

https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2006

用户界面画布

UI Canvas

每一个 UI 元素create 必须是UI Canvas的子项。要查看可以在 Unity 中创建的所有 UI 元素的列表,请从Hierarchy窗口中选择+ | UI,如以下屏幕截图所示

Every UI element you create must be a child of a UI Canvas. To see a list of all UI elements you can create within Unity, select + | UI from the Hierarchy window, as shown in the following screenshot:

图 6.1:Unity UI(uGUI)系统中可渲染的 UI 元素

图 6.1:Unity UI(uGUI)系统中可渲染的 UI 元素

Figure 6.1: The renderable UI elements within the Unity UI (uGUI) system

上图突出显示的每个 UI 项目都是可渲染的 UI 项目,必须包含在 Canvas 中才能渲染。如果您尝试将任何这些 UI 元素添加到不包含 Canvas 的场景中,则 Canvas 将自动添加到场景中,并且您尝试创建的项目将成为新添加的 Canvas 的子项。为了演示这一点,请尝试向空场景添加新的UI 文本元素。您可以通过选择+ | UI | Text来执行此操作。

Every one of the UI items highlighted in the preceding screenshot is a renderable UI item and must be contained within a Canvas to render. If you try to add any of those UI elements to a scene that does not contain a Canvas, a Canvas will automatically be added to the scene, and the item you attempted to create will be made a child of the newly added Canvas. To demonstrate this, try adding a new UI Text element to an empty scene. You can do so by selecting + | UI | Text.

这将导致三个新项目出现在层次结构列表中:CanvasTextEventSystem,其中 Text 是Canvas 的子项。

This will cause three new items to appear in the Hierarchy list: Canvas, Text, and EventSystem, where the Text is a child of the Canvas.

图 6.2:向场景中添加 UI 文本元素的结果

图 6.2:向场景中添加 UI 文本元素的结果

Figure 6.2: The result of adding a UI Text element to the scene

现在您的场景中已经有了一个 Canvas,您添加到场景中的任何新 UI 元素都将自动添加到此Canvas。

Now that you have a Canvas in your scene, any new UI elements you add to the scene will automatically be added to this Canvas.

笔记

Note

如果您尝试从 Canvas 中取出可渲染的 UI 元素,它将不会被绘制到场景中。

If you try to take a renderable UI element out of a Canvas, it will not be drawn to the scene.

您还可以通过选择+ | UI | Canvas来创建一个空的 Canvas 。当您在场景中创建新的 Canvas 时,如果场景中尚不存在EventSystem GameObject,则会自动为您创建一个(如您在前面的屏幕截图中看到的那样)。我们将在第 8 章中进一步讨论EventSystem GameObject ,但现在,您真正需要知道的是EventSystem允许您与 UI 项目进行交互

You can also create an empty Canvas by selecting + | UI | Canvas. When you create a new Canvas in a scene, if an EventSystem GameObject does not already exist within the scene, one will automatically be created for you (as you saw in the preceding screenshot). We’ll discuss the EventSystem GameObject further in Chapter 8, but for now, all you really need to know is the EventSystem allows you to interact with the UI items.

笔记

Note

场景中可以有多个 Canvas,每个 Canvas 都有自己的子项。

You can have more than one Canvas in your scene, each with its own children.

当你创建一个 Canvas 时,它会在你的场景中显示为一个大矩形。它会比那个大得多代表相机视图的矩形

When you create a Canvas, it will appear as a large rectangle within your scene. It will be significantly larger than that rectangle representing the camera’s view:

图 6.3:Unity UI(uGUI)系统中可渲染的 UI 元素

图 6.3:Unity UI(uGUI)系统中可渲染的 UI 元素

Figure 6.3: The renderable UI elements within the Unity UI (uGUI) system

Canvas 比 Camera 大,因为 Canvas 组件具有缩放模式。默认情况下,缩放模式相当于 UI 内的一个像素到一个 Unity 单位,因此它要大得多。这种大尺寸的一个好处是,您可以很容易地将 UI 项目视为一个独立的实体,这样就不会使相机视图变得混乱。

The Canvas is larger than the Camera because the Canvas component has a scaling mode on it. The scaling mode by default equates to one pixel within the UI to one Unity unit, so it’s a lot bigger. A nice consequence of this large size is that it is really easy to see your UI items as a somewhat separate entity, and this keeps it from cluttering up your camera view.

每个新创建的 Canvas 都会自动附带四个组件:Rect TransformCanvasCanvas ScalerGraphic Raycaster,如以下屏幕截图所示

Every newly created Canvas automatically comes with four components: Rect Transform, Canvas, Canvas Scaler, and Graphic Raycaster, as shown in the following screenshot:

图 6.4:Canvas 游戏对象的组件

图 6.4:Canvas 游戏对象的组件

Figure 6.4: The components of a Canvas GameObject

让我们逐一探索这些组件。

Let’s explore each of these components.

Rect Transform 组件

Rect Transform component

每个 Unity UI 游戏对象都有一个Rect Transform组件作为其第一个组成部分。这组件与非 UI 游戏对象上的Transform组件非常相似,因为它允许您将对象放置在场景中。

Every Unity UI GameObject has a Rect Transform component as its first component. This component is very similar to the Transform component on non-UI GameObjects in that it allows you to place the object within the scene.

您会注意到,当您第一次将 Canvas 放置在场景中时,您无法调整Rect Transform中的值,并且会显示一条消息,指出“某些值由 Canvas 驱动”,如上图所示。此消息意味着您无法控制 Canvas 的位置,因为Canvas组件中选择的属性决定了它们。

You’ll note that when you first place a Canvas in the scene, you can’t adjust the values within the Rect Transform, and there is a message stating, “Some values driven by Canvas”, as shown in the preceding screenshot. This message means you cannot control the position of the Canvas because the properties selected in the Canvas component determine them.

当 Canvas 组件具有将其渲染模式设置为屏幕空间-叠加屏幕空间-相机时Rect Transform组件的值的调整将被禁用。在这两种模式下,值由游戏显示的分辨率决定,因为 Canvas 填满了全屏区域。当 Canvas 的渲染模式设置为世界空间时,您可以根据需要调整值,因为此组件将确定其在场景中的位置。我们将在本文后面更详细地讨论如何使用Rect Transform组件章,但现在,让我们更彻底地回顾一下 Canvas 组件和各种渲染模式。

When a Canvas component has its Render Mode set to Screen Space-Overlay or Screen Space-Camera, the adjustment of the Rect Transform component’s values is disabled. In these two modes, the values are determined by the resolution of the game display because the Canvas fills up the full-screen area. When the Canvas’ Render Mode is set to World Space, you can adjust the values as you see fit, as this component will then determine its location within the scene. We will discuss how to use the Rect Transform Component more thoroughly later in this chapter, but for now, let’s review the Canvas component and the various render modes more thoroughly.

Canvas 组件

Canvas component

Canvas组件允许您可以从下拉菜单中选择 Canvas渲染模式。有三种渲染模式:屏幕空间-叠加屏幕空间-相机世界空间。不同的渲染模式决定了 UI 元素在场景中的绘制位置,以及如何使用Rect Transform组件。

The Canvas component allows you to select the Canvas Render Mode from a dropdown. There are three render modes: Screen Space-Overlay, Screen Space-Camera, and World Space. The different render modes determine where in the scene the UI elements will be drawn and how the Rect Transform component will be used.

在开发 UI 时,您应该始终做的第一件事就是根据需要适当地设置 Canvas 的渲染模式。如果场景中有多个 Canvas,则每个 Canvas 可以具有不同的渲染模式。更改渲染模式将更改 Canvas 组件上可用的属性。让我们回顾一下每种渲染模式的用途及其附带的属性

When developing your UI, the first thing you should always do is appropriately set your Canvas’ render mode based on your needs. If you have multiple Canvases in your scene, they can each have a different render mode. Changing the render mode will change the properties available on the Canvas component. Let’s review the purpose of each of the render modes and the properties that come with them.

屏幕空间叠加

Screen Space-Overlay

屏幕空间覆盖默认渲染模式。如果让你想象一个视频游戏的 UI,你很可能会想象一个在屏幕空间覆盖中渲染的UI。此渲染模式将 Canvas 中的所有 UI 元素覆盖在场景中的所有内容前面,就像在屏幕顶部绘制一样。因此,UI 项目平视显示器HUD)和与屏幕出现在同一平面上的弹出窗口将包含在屏幕空间覆盖画布内。

Screen Space-Overlay is the default render mode. If you are asked to think of a video game UI, it’s highly likely that you will envision one rendered in Screen Space-Overlay. This render mode overlays all the UI elements within the Canvas in front of everything in the scene as if it is drawn on top of the screen. So, UI items like heads-up-displays (HUDs) and pop-up windows that appear on the same plane as the screen will be contained within a Screen Space-Overlay Canvas.

请记住,当 Canvas 使用屏幕空间叠加渲染模式时,您无法调整Canvas 的Rect Transform组件。这是因为画布将根据以下情况自动调整大小:屏幕(不是相机)的尺寸。

Remember, when a Canvas is using the Screen Space-Overlay render mode, you cannot adjust the Rect Transform component of the Canvas. This is because the canvas will automatically resize based on the size of the screen (not the camera).

图 6.5:屏幕空间 – 叠加渲染模式属性

图 6.5:屏幕空间 – 叠加渲染模式属性

Figure 6.5: Screen Space – Overlay Render Mode properties

当您选择“屏幕空间叠加”时,以下属性将可用:

When you have Screen Space-Overlay selected, the following properties become available:

  • Pixel Perfect:选中此复选框将使 Canvas 中渲染的元素与像素对齐。它可以使 UI 元素看起来更清晰,模糊度更低。这可能会导致性能问题,因此仅在绝对必要时才使用它。
  • Pixel Perfect: Selecting this check box will cause the elements rendered in the Canvas to line up with pixels. It can make the UI elements appear sharper and less blurry. This can cause performance issues, so only use it if absolutely necessary.
  • 排序顺序:此选项将决定场景中所有屏幕空间叠加画布的渲染顺序。您可以将其视为堆叠顺序。数字越高,画布中的项目就越接近观看场景的人。换句话说,排序顺序较高的画布将出现在排序顺序较低的画布之上。
  • Sort Order: This option will determine the order in which all the Screen Space-Overlay Canvases in your scene will render. You can think of it as a stacking order. The higher the number, the closer the items within the Canvas will appear to the person viewing the scene. In other words, higher sort order numbered canvases will appear on top of lower sort order numbered canvases.
  • 目标显示器:如果您正在创建 PC、Mac、Linux 独立游戏,您可以在最多 8 个不同的显示器上显示不同的摄像头。您还可以为每个显示器设置不同的 UI。在这里,您将告诉 Canvas 它将渲染在哪个显示器上。
  • Target Display: If you are creating a PC, Mac, Linux Standalone game, you can have different cameras display on up to eight different monitors. You can also have different UI for each monitor. This is where you will tell the Canvas which of the displays it will render on.
  • 附加着色器通道:着色器本质上是基于光和材质描述游戏对象颜色的算法。每个 Canvas 自动包含以下着色器通道:Position、Color 和 Uv0。但是,此属性允许您添加其他通道。着色器是一个相当复杂的主题,因此我们不会在本文中花了很多时间讨论它们。在屏幕空间叠加模式下,下拉菜单中可用的通道并非全部有效,您将在组件中看到一条消息,描述哪些通道无效。
  • Additional Shader Channels: Shaders are essentially algorithms that describe the color of a GameObject based on light and material. Each Canvas automatically includes the following shader channels: Position, Color, and Uv0. However, this property lets you add additional channels. Shaders are a pretty complicated topic, so we won’t spend a lot of time discussing them in this text. In Screen Space-Overlay mode, not all of the channels available in the dropdown menu work, and you’ll see a message in the component describing which are not working.

接下来我们看一下屏幕空间相机

Next, let’s look at Screen Space Camera.

屏幕空间-相机

Screen Space-Camera

屏幕空间-相机的性能与屏幕空间-叠加类似,但它将所有 UI 元素渲染为距离相机特定距离。从以下屏幕截图中可以看到,如果没有选择渲染相机,则此渲染模式的工作方式与屏幕空间叠加模式完全相同(如警告消息所示):

Screen Space-Camera performs similarly to Screen Space-Overlay, but it renders all UI elements as if they are a specific distance away from the camera. As you can see from the following screenshot, if there is no Render Camera selected, this render mode works exactly like the Screen Space-Overlay mode (as indicated by the warning message):

图 6.6:屏幕空间的警告消息 - 相机渲染模式

图 6.6:屏幕空间的警告消息 - 相机渲染模式

Figure 6.6: Warning message for Screen Space – Camera Render Mode

您可以在屏幕空间相机渲染模式中将场景中的任何相机分配给渲染相机。这是画布将绘制到的相机。将相机添加到渲染相机插槽后,警告消息将消失,并且将为您提供新的选项,如以下屏幕截图所示:

You can assign any camera in your scene to the Render Camera in the Screen Space-Camera render mode. This is the camera to which the canvas will draw. Once you add a camera to the Render Camera slot, the warning message will disappear, and new options will be made available to you, as shown in the following screenshot:

图 6.7:屏幕空间 – 相机渲染模式属性

图 6.7:屏幕空间 – 相机渲染模式属性

Figure 6.7: Screen Space – Camera Render Mode properties

当你选择屏幕空间-相机时,以下属性可用:

When you have Screen Space-Camera selected, the following properties become available:

  • 像素完美:这是与屏幕空间覆盖相同的选项。
  • Pixel Perfect: This is the same option as with Screen Space-Overlay.
  • 渲染相机:如前所述,这是画布将绘制到的相机。
  • Render Camera: As stated earlier, this is the camera to which the canvas will draw.
  • 平面距离:此属性告诉画布它应该显示距离相机多远
  • Plane Distance: This property tells the Canvas how far away from the camera it should display.
  • 排序层:此属性允许您选择使用哪个Sprite 排序层来显示画布
  • Sorting Layer: This property allows you to choose which Sprite Sorting Layer to display the Canvas with.
  • 图层顺序:此属性决定了画布在Sprite 排序图层(先前选择)中的显示顺序。此顺序的工作方式与排序顺序的工作方式类似,数字越大,数字越小。
  • Order in Layer: This property determines the order in the Sprite Sorting Layer (chosen earlier) that the Canvas will display. This order works similar to the way Sort Order works, with higher numbers appearing on top of lower numbers.
  • 附加着色器通道:此属性的工作方式与在屏幕空间叠加中相同
  • Additional Shader Channels: This property works as it does in Screen Space-Overlay.

如果您希望 Canvas 从与主摄像头不同的角度进行渲染,此渲染模式非常有用。在 2D 游戏中,创建与摄像头一致缩放的静态背景也很有用。由于您可以在此渲染模式下使用Sprite Sorting Layer,因此您可以确保包含背景的 Canvas 始终在场景中所有其他对象的后面进行渲染。

This rendering mode is helpful if you want a Canvas to render from a different perspective than that of your main camera. It is also helpful for creating a static background that will consistently scale with the camera in a 2D game. Since you can use Sprite Sorting Layer with this rendering mode, you can make sure the Canvas containing the background always renders behind all other objects in the scene.

请记住,当 Canvas 是使用屏幕空间-相机渲染模式时,您无法调整画布的Rect Transform组件。这是因为画布会根据相机(而不是屏幕)的大小自动调整大小。

Remember, when a Canvas is using the Screen Space-Camera render mode, you cannot adjust the Rect Transform component of the Canvas. This is because the canvas will automatically resize based on the size of the camera (not the screen).

世界空间

World Space

最后一种渲染模式是World Space。此模式允许您渲染 UI 元素,就好像它们物理上位于世界。

The last rendering mode is World Space. This mode allows you to render UI elements as if they are physically positioned within the world.

屏幕空间叠加屏幕空间相机中,您无法调整Rect Transform组件的属性。具有这两种渲染模式的 Canvas 中的 UI 元素的位置不会转换为世界空间坐标,而是相对于屏幕和相机。但是,当 Canvas 处于世界空间渲染模式时,可以调整Rect Transform的值,因为 UI 元素的坐标基于场景内的实际位置。这些 Canvas 不必像其他两种 Canvas类型那样面向指定的相机。

In Screen Space-Overlay and Screen Space-Camera, you cannot adjust the properties of the Rect Transform component. The positions of UI elements within Canvases with those two rendering modes do not translate to world space coordinates and are instead relative to the screen and camera. However, when a Canvas is in World Space render mode, the values of the Rect Transform can be adjusted because the coordinates of the UI elements are based on actual positions within the scene. These Canvases do not have to face a specified camera as the other two Canvas types do.

图 6.8:渲染模式 – 世界空间属性

图 6.8:渲染模式 – 世界空间属性

Figure 6.8: Render Mode – World Space properties

此模式请求事件相机,而不是像屏幕空间相机模式那样请求渲染相机。事件相机不同于渲染相机。由于此画布位于世界空间中,它将使用主相机进行渲染,就像场景中存在的所有其他对象一样。事件相机是将从事件系统接收事件的相机。因此,如果此画布上的项目需要交互,则必须包含事件相机。如果玩家不会与画布上的项目交互,则可以将其留空。

This mode requests an Event Camera, rather than a Rendering Camera as Screen Space-Camera mode requested. An Event Camera is different than a Rendering Camera. Since this Canvas is in the World Space, it will be rendered with the Main Camera, just as all the other objects that exist within the scene. The Event Camera is the camera that will receive events from the EventSystem. So, if items on this Canvas require interactions, you have to include an Event Camera. If the player won’t be interacting with the items on the Canvas, you can leave this blank.

您需要指定事件相机的原因是光线投射。当用户点击或触摸屏幕时,光线(单向线)会从点击点(或触摸点)无限向前投射到场景中。它指向的方向由相机面向的方向决定。大多数情况下,您会将其设置为主相机,因为这是玩家将会期待事件的发生。

The reason you need to specify an Event Camera is ray casting. When the user clicks on or touches the screen, a ray (one-directional line) is cast infinitely forward from the point of click (or touch) into the scene. The direction it points is determined by the direction the camera is facing. Most of the time, you will set this as your Main Camera because that is the direction in which the player will expect the events to occur.

当您选择“世界空间”时,以下属性将可用:

When you have World Space selected, the following properties become available:

  • 事件相机:如前所述,分配给此插槽的相机决定哪个相机将接收画布的事件
  • Event Camera: As stated earlier, the camera assigned to this slot determines which camera will receive the events of the Canvas
  • 排序层:此属性与屏幕空间相机中的相同
  • Sorting Layer: This property is the same as in Screen Space-Camera
  • 图层顺序:此属性与屏幕空间相机中的顺序相同
  • Order in Layer: This property is the same as in Screen Space-Camera
  • 附加着色器通道:此属性的工作方式与屏幕空间叠加中的相同
  • Additional Shader Channels: This property works as it does in Screen Space-Overlay

接下来我们看一下Canvas Scalar组件。

Next, let us look at the Canvas Scalar component.

Canvas Scalar 组件

Canvas Scalar component

Canvas Scalar组件决定画布内项目的缩放方式。它还决定像素密度UI Canvas内的项目

The Canvas Scalar component determines how the items within the canvas will scale. It also determines the pixel density of the items within the UI Canvas.

第 1 章中,我们讨论了如何以单一分辨率或单一宽高比构建游戏。但是,大多数时候,您无法选择游戏的分辨率或宽高比。您会注意到,我只提到为 PC、Mac 和 Linux 独立版本和 WebGL 版本指定宽高比和分辨率。当您构建将在手持屏幕或电视屏幕上播放的内容时,您无法保证该屏幕的大小

In Chapter 1, we discussed how to build your game at a single resolution or a single aspect ratio. However, most of the time, you will not have the luxury of choosing the resolution or aspect ratio of your game. You’ll note that I only mentioned specifying the aspect ratio and resolution for the PC, Mac, and Linux Standalone builds and the WebGL builds. When you build to something that will play on a handheld screen or a TV screen, you cannot guarantee how big that screen will be.

由于您无法保证游戏的分辨率或宽高比,因此 UI 需要适应各种分辨率和缩放比例;这就是Canvas Scalar 组件存在的原因。

Due to the fact that you cannot guarantee the resolution or aspect ratio of your game, the need for your UI to adjust to various resolutions and scaling is very important; that’s why this Canvas Scalar component exists.

Canvas Scalar组件有四种UI缩放模式

The Canvas Scalar component has four UI Scale Modes:

  • 恒定像素大小
  • Constant Pixel Size
  • 根据屏幕尺寸缩放
  • Scale With Screen Size
  • 恒定的物理尺寸
  • Constant Physical Size
  • 世界
  • World

前三种UI 缩放模式在 Canvas渲染模式设置为屏幕空间-叠加屏幕空间-相机时可用。第四种UI 缩放模式Canvas渲染模式设置为世界空间放)。

The first three UI Scale Modes are available when the Canvas Render Mode is set to Screen Space-Overlay or Screen Space-Camera. The fourth UI Scale Mode is automatically assigned when the Canvas Render Mode is set to World Space (it cannot be changed when set).

恒定像素大小

Constant Pixel Size

当画布的UI 缩放模式设置为恒定像素大小时,UI 中的每个项目都将保持其原始像素无论屏幕大小如何,尺寸都会缩放。您会注意到这是默认设置,因此默认情况下 UI 不会缩放;您必须打开该设置才能通过更改屏幕分辨率进行缩放。

When a Canvas has the UI Scale Mode set to Constant Pixel Size, every item in the UI will maintain its original pixel size regardless of the size of the screen. You’ll note that this is the default setting, so by default UI does not scale; you must turn the setting on for it to scale by altering screen resolutions.

图 6.9:恒定像素大小 UI 缩放模式属性

图 6.9:恒定像素大小 UI 缩放模式属性

Figure 6.9: Constant Pixel Size UI Scale Mode properties

当您将UI 缩放模式更改为恒定像素大小时,您将看到检查器中出现以下属性:

When you change the UI Scale Mode to Constant Pixel Size, you will see the following properties appear within the inspector:

  • 比例因子:此设置为 UI 内的所有对象创建比例倍数。例如,如果将此数字设置为2,则 UI 内的所有内容都将大小翻倍。如果将此数字设置为0.5,则所有项目的大小将减半。
  • Scale Factor: This setting creates a scale multiple for all objects within the UI. For example, if you set this number to 2, everything within the UI will then double in size. If you set this number to 0.5, all items will have their size halved.
  • 每单位参考像素:此设置确定单个游戏单位占用多少个像素。当此数字设置为100时,这意味着两个相距一个游戏单位的对象将相距 100 像素。换句话说,如果两个对象位于相同的y坐标,但一个对象的x坐标为1,另一个对象的x坐标为2,它们相距恰好 100 像素。此属性在所有其他模式下的工作方式相同,因此在下一节中我不会讨论它。
  • Reference Pixels Per Unit: This setting determines how many pixels take up a single in-game unit. When this number is set to 100, that means two objects that are one game unit apart from each other will be 100 pixels apart. Put another way, if two objects are at the same y-coordinate, but one object has an x-coordinate of 1, and the other has an x-coordinate of 2, they are exactly 100 pixels apart. This property works the same way in all other modes, so I will not discuss it in the next sections.

根据屏幕尺寸缩放

Scale With Screen Size

当您将Canvas Scalar组件设置为“随屏幕尺寸缩放”时,Canvas 上的 UI 元素将根据“参考分辨率”缩放。如果屏幕大于或小于“参考分辨率”值,则 Canvas 上的项目将放大或缩小因此。在第 1 章中,我告诉过你,你应该决定一个代表你的 UI 设计的理想分辨率的默认分辨率。这个默认分辨率将是参考分辨率

When you have the Canvas Scalar component set to Scale with Screen Size, UI elements on the Canvas will scale based on a Reference Resolution. If the screen is larger or smaller than the Reference Resolution value, the items on the Canvas will then scale up or down accordingly. In Chapter 1, I told you that you should decide on a default resolution that represents the ideal resolution of your UI design. This default resolution will be the Reference Resolution.

图 6.10:按屏幕尺寸缩放的 UI 缩放模式属性

图 6.10:按屏幕尺寸缩放的 UI 缩放模式属性

Figure 6.10: Scale With Screen Size UI Scale Mode properties

如果屏幕的宽高比与参考分辨率值匹配,则项目可以毫无问题地放大和缩小。如果不匹配,则需要使用Canvas Scalar组件来定义项目在宽高比发生变化时如何缩放。

If the aspect ratio of your screen matches the Reference Resolution value, then things will scale up and down without any problems. If it does not match, then you need to use the Canvas Scalar component to define how items will scale if the aspect ratio changes.

这可以通过使用屏幕匹配模式设置来实现。以下列表是三种不同的屏幕匹配模式,它们决定了当游戏的宽高比与参考分辨率 宽高比不匹配时画布将如何缩放:

This can be done by using the Screen Match Mode settings. In the following list are the three different Screen Match Modes that determine how the Canvas will scale if the game’s aspect ratio does not match the Reference Resolution aspect ratio:

  • 匹配宽度或高度:这将根据参考高度或参考宽度缩放 UI。它可以也根据两者的结合进行扩展。
  • Match Width Or Height: This will scale the UI with respect to the reference height or the reference width. It can also scale based on a combination of both.
  • 扩大:如果屏幕小于参考分辨率,则画布将扩大以匹配参考分辨率
  • Expand: If the screen is smaller than the Reference Resolution, the canvas will be expanded to match that of the Reference Resolution.
  • 缩小:如果屏幕大于参考分辨率,则画布将缩小以匹配参考分辨率
  • Shrink: If the screen gets larger than the Reference Resolution, the canvas will be reduced to match that of the Reference Resolution.

扩展和收缩屏幕匹配模式没有其他属性可供编辑;它们只是“做自己的事情”。但是,匹配宽度高度屏幕匹配模式确实有一个匹配属性。此属性是一个滑动比例,可以在0宽度)和1高度)之间调整

The Expand and Shrink Screen Match Modes do not have any further properties to edit; they just “do their own thing.” However, the Match Width Or Height Screen Match Mode does have a Match property. This property is a sliding scale that can be adjusted between 0 (Width) and 1 (Height).

图 6.11:按屏幕尺寸缩放的 UI 缩放模式属性

图 6.11:按屏幕尺寸缩放的 UI 缩放模式属性

Figure 6.11: Scale With Screen Size UI Scale Mode properties

当Match的值设置为0时,Canvas Scaler将强制 Canvas 始终具有由Reference Resolution指定的相同宽度。这将保持对象沿 Canvas 宽度的相对比例和位置。因此,对象在水平方向上不会彼此远离或靠近。但是,它将完全忽略高度。因此,对象可以在垂直方向上彼此远离或靠近

When the value of Match is set to 0, the Canvas Scaler will force the Canvas to always have the same width specified by the Reference Resolution. This will maintain the relative scales and positions of objects along the width of the Canvas. So, objects will not get further away from or closer to each other in the horizontal direction. However, it will completely ignore the height. So, objects can get further from or closer to each other in the vertical direction.

将匹配值设置为1将完成相同的操作,但将保持对象沿高度而不是宽度的位置和比例。

Setting the Match value to 1 will accomplish the same thing but will maintain the positions and scales of the objects along the height, not the width.

将匹配值设置为0.5会将游戏的宽度和高度与参考分辨率进行比较,并尝试在水平和垂直方向上保持物体之间的距离。

Setting the Match value to 0.5 will compare the game’s width and height to that of Reference Resolution, and it will try to maintain the distances between objects in both the horizontal and vertical directions.

Match值可以是01之间的任意数字。如果数字接近1 ,缩放将有利于高度,如果数字接近0,则缩放将有利于宽度。

The Match value can be any number between 0 and 1. If the number is closer to 1, scaling will favor the height, and if it is closer to 0, it will favor the width.

这些Match设置并非适合所有游戏的所有宽高比和分辨率。您可以选择将取决于您希望 UI 如何缩放。如果您希望保持相对垂直位置,请使用高度( 1 )。如果您希望保持相对水平位置,请使用宽度( 0 )。这实际上取决于您最关心哪个间距

None of these Match settings will be perfect for all games at all aspect ratios and resolutions. The settings you choose will depend on how you want the UI to scale. If you want the relative vertical positions to be maintained, use Height (1). If you want the relative horizontal positions to be maintained, use Width (0). It really just depends on which spacing you care the most about.

我建议根据您的游戏方向使用以下设置:

I recommend using the following settings based on the orientation of your game:

方向

Orientation

匹配值

Match Value

肖像

Portrait

0(宽度)

0 (Width)

景观

Landscape

1(高度)

1 (Height)

各不相同

Varies

0.5(宽度和高度)

0.5 (Width and Height)

表 6.1:方向和匹配值

Table 6.1: Orientation and Match Value

我根据参考分辨率上的两个数字中最小的一个来选择这些设置。在纵向模式下,宽度将最小,因此我发现保持项目在宽度上的相对位置很重要。这是个人偏好,只是建议,不一定适用于所有游戏。但是,我发现这对大多数游戏来说都是一个很好的经验法则

I chose these settings based on whichever of the two numbers on the Reference Resolution is the smallest. In Portrait mode, the width will be the smallest, so I find it important to maintain the relative position of the items in the width. This is a personal preference and just a recommendation, and it will not necessarily make sense for all games. However, I have found it to be a good rule of thumb for most games.

最好避免制作在纵向和横向模式之间变化的游戏,除非您拥有最少的 UI 或非常擅长创建可扩展的 UI。

It is best to avoid making games that will vary between portrait and landscape mode unless you have minimal UI or are very comfortable with creating scalable UI.

恒定的物理尺寸

Constant Physical Size

当画布的UI 缩放模式设置为恒定物理尺寸时,无论屏幕尺寸如何,UI 中的每个项目都将保持其原始物理尺寸。物理尺寸指的是将显示的尺寸就像用户拿出尺子并在屏幕上测量一样。与“恒定像素大小”非常相似,具有此UI 缩放模式设置的画布上的项目不会缩放。

When a Canvas has the UI Scale Mode set to Constant Physical Size, every item in the UI will maintain its original physical size, regardless of the size of the screen. Physical size references the size that will appear to the user if they were to take out a ruler and measure it on their screen. Much like Constant Pixel Size, items on Canvases with this UI Scale Mode setting will not scale.

如果您希望某个 UI 项目始终具有特定的宽度和高度,则可以将其放在具有此UI 缩放模式的 Canvas 上。例如,如果您希望按钮始终为 2 英寸宽和 1 英寸高,则可以使用此模式。这在手机游戏中特别有用,它允许您根据我们在第 4 章中讨论的建议确保按钮的尺寸足够大,适合人类手指

If you had a UI item that you wanted to always be a specific width and height, you’d put it on a Canvas that has this UI Scale Mode. For example, if you wanted a button to always be 2 inches wide and 1 inch tall, you’d use this mode. This is particularly helpful in mobile games, allowing you to make sure buttons are sized large enough for human fingers based on the recommendations we discussed in Chapter 4.

图 6.12:使用恒定物理尺寸缩放 UI 缩放模式属性

图 6.12:使用恒定物理尺寸缩放 UI 缩放模式属性

Figure 6.12: Scale with Constant Physical Size UI Scale Mode properties

当您将UI 缩放模式更改为恒定物理尺寸时,您将看到检查器中出现以下属性:

When you change the UI Scale Mode to Constant Physical Size, you will see the following properties appear within the inspector:

  • 物理 单位测量单位。您可以从厘米毫米英寸派卡中选择。
  • Physical Unit: The unit of measure. You can select from Centimeters, Millimeters, Inches, Points, and Picas.
  • 后备屏幕 DPI:如果 DPI 未知,则这是假定的 DPI。
  • Fallback Screen DPI: If the DPI is unknown, this is the assumed DPI.
  • 默认精灵 DPI:所有每单位像素数等于参考单位像素数值的精灵的 DPI。
  • Default Sprite DPI: The DPI of all sprites with Pixels Per Unit that are equal to the Reference Pixels Per Unit value.

世界

World

世界是唯一适用于画布的UI 缩放模式设置为World Space。从以下屏幕截图中你会看到该模式无法更改:

World is the only UI Scale Mode available for Canvases set to World Space. You’ll see from the following screenshot that the mode cannot be changed:

图 6.13:使用 World UI Scale Mode 属性进行缩放

图 6.13:使用 World UI Scale Mode 属性进行缩放

Figure 6.13: Scale with World UI Scale Mode properties

当您将UI Scale Mode更改为World时,您将看到检查器中出现以下属性:

When you change the UI Scale Mode to World, you will see the following property appear within the inspector:

  • 每单位动态像素数:这是所有动态 UI 项目(例如文本)的每单位像素设置。
  • Dynamic Pixels Per Unit: This is the Pixels Per Unit setting for all dynamic UI items (such as text).

图形 Raycaster 组件

Graphic Raycaster component

Graphic Raycaster组件允许您检查 Canvas 上的对象是否已被用户输入击中使用事件系统。正如在查看世界空间画布渲染模式时所讨论的那样,当用户触摸屏幕,从玩家触摸的屏幕点向前投射一条射线。Graphic Raycaster检查这些射线,看看它们是否击中了画布上的某个物体。

The Graphic Raycaster component allows you to check to see whether objects on the Canvas have been hit by a user input using the Event System. As discussed when looking at the World Space Canvas Render Mode, when a user touches the screen, a ray is cast forward from the point on the screen at which the player touches. The Graphic Raycaster checks these rays and sees if they hit something on the Canvas.

图 6.14:Graphic Raycaster 组件

图 6.14:Graphic Raycaster 组件

Figure 6.14: The Graphic Raycaster component

您可以在Graphic Raycaster组件上调整以下属性

You can adjust the following properties on the Graphic Raycaster component:

  • 忽略反向图形:如果 UI 元素背对玩家,则选中此选项将阻止命中。如果未选中此选项,命中将记录在背对的UI 对象上。
  • Ignore Reversed Graphics: If a UI element is facing away from the player, having this selected will stop the hit from registering. If it is not selected, hits will register on back-facing UI objects.
  • 阻挡物体:此设置指定其前方的哪些类型的物体会阻挡其被击中。因此,如果您选择二维,则此画布上物体前方的任何二维物体都会阻止物体与其交互。但是,3D 物体不会阻止交互。可能的选项如下所示:
  • Blocking Objects: This setting specifies which types of items in front of it will block it from being hit. So, if you select Two D, any Two D object in front of the items on this Canvas will stop the items from being interacted with. However, 3D objects will not stop the interaction. The possible options are shown here:
图 6.15:Graphics Raycaster Blocking Objects 选项

图 6.15:Graphics Raycaster Blocking Objects 选项

Figure 6.15: Graphics Raycaster Blocking Objects options

  • 阻塞掩码:此属性上的选择项目与“阻塞对象”属性类似。这允许您根据渲染层选择项目,因此您可以更具体一点。可能的选项如下
  • Blocking Mask: Selecting items on this property works similarly to the Blocking Objects property. This allows you to select items based on their Rendering Layer, so you can get a little more specific. The possible options are shown as follows:
图 6.16:图形光线投射阻挡遮罩选项

图 6.16:图形光线投射阻挡遮罩选项

Figure 6.16: Graphics Raycaster Blocking Mask options

我们将讨论光线投射和在第 8 章中更详细地介绍了事件系统

We will discuss Raycasting and the Event System more thoroughly in Chapter 8.

Canvas Renderer 组件

Canvas Renderer component

Canvas Renderer组件是不是在 Canvas GameObject 上,而是在所有可渲染的UI 对象。

The Canvas Renderer component is not on a Canvas GameObject but on all renderable UI objects.

图 6.17:Canvas Renderer 组件

图 6.17:Canvas Renderer 组件

Figure 6.17: The Canvas Renderer component

要渲染 UI 元素,它必须具有Canvas Renderer组件。您通过+ | UI菜单创建的所有可渲染 UI 元素都会自动附加此组件。如果您尝试删除此组件,您将看到类似以下内容的警告:

For a UI element to render, it must have a Canvas Renderer component on it. All the renderable UI elements that you create via the + | UI menu will automatically have this component attached to them. If you try to remove this component, you will see a warning similar to the following:

图 6.18:尝试删除 Canvas Renderer 组件时出现的警告消息

图 6.18:尝试删除 Canvas Renderer 组件时出现的警告消息

Figure 6.18: Warning message when you try to remove a Canvas Renderer component

在上面的截图中,我尝试从 Text UI 对象中删除 Canvas Renderer 组件。如您所见,它不允许我删除 Canvas Renderer 组件,因为文本组件依赖它。如果我返回并删除 Text 组件,然后我就可以删除 Canvas Renderer 组件。

In the preceding screenshot, I tried to remove the Canvas Renderer component from a Text UI object. As you can see, it will not let me remove the Canvas Renderer component because the Text component relies on it. If I return and remove the Text component, I would then be able to remove the Canvas Renderer component.

Canvas Renderer组件上的唯一属性是Cull Transparent Mesh切换。“To cull”表示不绘制。因此,此属性表示渲染器不会绘制顶点颜色 alpha 为 0 或接近0 的任何几何图形。

The only property on the Canvas Renderer component is the Cull Transparent Mesh toggle. “To cull” means to not draw. So, this property states that the renderer will not draw any geometry that has its vertex color alpha at or close to 0.

UI面板

UI Panel

UI 面板的主要功能是容纳其他 UI 元素。您可以通过选择+ | UI | Panel来创建面板。重要的是请注意,没有 Panel 组件。Panel 实际上只是具有Rect TransformCanvas RendererImage组件的游戏对象。因此,UI Panel 实际上只是一个带有一些预定义属性的 UI Image

The main function of UI Panels is to hold other UI elements. You can create a Panel by selecting + | UI | Panel. It’s important to note that there is no Panel component. Panels are really just GameObjects that have Rect Transform, Canvas Renderer, and Image components. So, really, a UI Panel is just a UI Image with a few properties predefined for it.

图 6.19:Panel GameObject 上的组件

图 6.19:Panel GameObject 上的组件

Figure 6.19: The components on a Panel GameObject

默认情况下,面板以背景图像(只是一个灰色圆角矩形)作为具有中等不透明度的源图像。您可以用另一幅图像替换源图像完全删除该图像。

By default, Panels start with the Background Image (which is just a grey rounded rectangle) as the Source Image with medium opacity. You can replace the Source Image with another Image or remove the image entirely.

面板非常有用,当你尝试确保项目能够缩放并相对于彼此适当定位。包含在同一个面板内的项目将相对于面板缩放,并在此过程中保持彼此的相对位置。

Panels are very useful when you are trying to ensure that items scale and are appropriately positioned relative to each other. Items that are contained within the same Panel will scale relative to the Panel and maintain their relative position to each other in the process.

我们很快会更彻底地了解图像组件,但是现在我们正在查看一个允许我们编辑其矩形变换组件的对象,让我们探索该组件。

We will look at the Image component more thoroughly soon, but now that we are looking at an object that will allow us to edit its Rect Transform component, let›s explore that component.

矩形变换

Rect Transform

每个 UI 元素都有一个Rect Transform组件。Rect Transform组件的工作原理与Transform组件非常相似,用于确定对象的位置它所附着于其上。

Each UI element has a Rect Transform component. The Rect Transform component works very similarly to the Transform component and is used to determine the position of the object on which it is attached.

矩形工具

Rect Tool

任何变换工具都可用于操作 UI 对象。不过,矩形工具可让您缩放、移动和通过操纵矩形来旋转任何物体涵盖了它。虽然此工具可以用于 3D 对象,但它对 2D 和UI 对象最有用。

Any of the Transform tools can be used to manipulate UI objects. However, the Rect Tool allows you to scale, move, and rotate any object by manipulating the rectangle that encompasses it. While this tool can be used with 3D objects, it is most useful for 2D and UI objects.

图 6.20:矩形工具

图 6.20:矩形工具

Figure 6.20: The Rect Tool

  • 要使用矩形工具移动对象,请选择该对象,然后单击并拖动矩形内。
  • To move an object with the Rect Tool, select the object and then click and drag inside the rectangle.
  • 要调整对象大小,请将鼠标悬停在对象的边缘或角落上。当光标变为箭头时,单击并拖动以调整对象大小。拖动时按住Shift键可以均匀缩放。
  • To resize an object, hover over the edge or corner of an object. When the cursor changes to arrows, click and drag to resize the object. You can scale uniformly by holding down the shift button while dragging.
  • 要旋转,请将鼠标悬停在对象的角落 - 略微超出矩形,直到光标在其角落显示一个旋转圆圈。然后您可以通过单击和拖动来旋转。
  • To rotate, hover at the corner of the objects--slightly outside of the rectangle until the cursor displays a rotating circle at its corner. You can then rotate by clicking and dragging.

定位模式

Positioning modes

使用矩形工具时,选择正确的定位模式非常重要。您可以选择CenterPivot以及GlobalLocal。单击以下按钮可切换模式:

When using the Rect Tool, it is important that you have the correct positioning modes selected. You can select Center or Pivot and Global or Local. The modes will toggle by clicking on the buttons:

图 6.21:各种定位模式

图 6.21:各种定位模式

Figure 6.21: The various positioning modes

  • 当处于中心模式时,物体将基于其中心点移动并绕其中心点旋转。
  • When in Center mode, the object will move based on its center point and rotate around its center point.
  • 枢轴模式下,物体将围绕其枢轴点而不是中心点旋转。在此模式下,您还可以通过将鼠标悬停在枢轴点上并单击和拖动来移动来更改枢轴点的位置。
  • When in Pivot mode, the object will rotate around its pivot point rather than its center point. You can also alter the position of the pivot point in this mode by hovering over the pivot point and clicking and dragging to move.
  • 全局模式下,矩形变换的边界框将是一个非旋转的框,其中包含整个物体
  • When in Global mode, the Rect Transform’s bounding box will be a non-rotated box that encompasses the entire object.
  • 本地模式下,矩形变换的边界框将是一个与对象紧密贴合的旋转框。
  • When in Local mode, the Rect Transform’s bounding box will be a rotated box that snuggly fits the object.

下图显示了全局局部模式下面板的矩形变换的边界框。空心蓝色圆圈表示对象的枢轴点:

The following illustration shows the bounding boxes of the Rect Transform for a Panel in Global and Local modes. The empty blue circle represents the object’s pivot point:

图 6.22:全局模式与局部模式

图 6.22:全局模式与局部模式

Figure 6.22: Global mode vs. Local mode

接下来我们看一下Rect Transform组件。

Next, let’s look at the Rect Transform component.

Rect Transform 组件

Rect Transform component

如前所述,UI 元素没有标准Transform组件;它们具有Rect Transform组件。如果如果你将它与标准Transform组件进行比较,你会发现它具有相当多的属性:

As stated earlier, UI elements do not have the standard Transform component; they have the Rect Transform component. If you compare it to a standard Transform component, you will see that it has quite a few more properties:

图 6.23:变换与矩形变换

图 6.23:变换与矩形变换

Figure 6.23: Transform vs. Rect Transform

您可以使用它来更改位置、旋转和缩放,就像使用Transform一样,但还增加了Anchor Presets属性(由左上角的方形图像表示)、两个用于确定尺寸的值(Anchor MinMax点)和Pivot点。需要注意的是,Scale值被视为局部比例。如果您使用 Rect Tool 重新缩放对象,即使使用 Local 定位模式, Scale中的值仍将保持1

You can use it to change the position, rotation, and scale, just as you can with Transform, but there are the added properties of Anchor Presets (represented by the square image in the top left corner), two values for determining dimension (Anchor Min and Max points), and the Pivot point. It’s important to note that the Scale value is considered the local scale. If you rescale the object with the Rect Tool, even with the Local positioning mode, the values within Scale will remain at 1.

你可能已经注意到上图中的位置和尺寸值标签与UI 面板属性部分提供的标签不同。这是因为表示位置和尺寸的标签会根据所选的锚点预设而变化。我们稍后将讨论如何使用这些锚点预设,但让我们看一些位置和尺寸值可以容纳的标签的不同示例。

You may have noticed that the labels for the position and dimensional values are different in the preceding illustration than they are in the one provided in the UI Panel Properties section. This is because the labels that represent the position and dimension change depending on the Anchor Preset chosen. We’ll discuss how to use these Anchor Presets momentarily, but let’s look at some different examples of labels the position and dimensional values can hold.

图 6.24:矩形变换属性的变化

图 6.24:矩形变换属性的变化

Figure 6.24: Variation in Rect Transform properties

如果Rect Transform的 Anchor Preset 设置为不包含拉伸,如上图左上角的示例,位置的值由Pos XPox YPos Z决定,尺寸由Width Height决定。

If the Rect Transform has its Anchor Preset set to not include stretch, as with the top-left example in the preceding screenshot, the values for position are determined by Pos X, Pox Y, Pos Z, and the dimensions are determined by Width and Height.

如果Rect Transform的 Anchor Preset 设置为包含拉伸,与其他三个示例一样,则垂直于拉伸的位置和平行于拉伸的尺寸将标记为LeftRightTopBottom 。这些值表示与父级Rect Transform边框的偏移量

If the Rect Transform has its Anchor Preset set to include stretch, as with the other three examples, the positions perpendicular to the stretch and the dimensions parallel to the stretch are labeled with Left, Right, Top, and Bottom. These values represent the offset from the border of the parent’s Rect Transform.

对象的锚点决定了所有相对位置的测量起点。枢轴点决定了缩放和旋转修改器测量起点发生。它将围绕该点旋转并朝该点缩放。我们将在“锚点和枢轴”部分更彻底地研究锚点和枢轴。

The Anchor point of an object determines the point from which all the relative positions are measured from. The Pivot point determines the point from which the scaling and rotating modifiers happen. It will rotate around this point and scale toward this point. We will look at Anchors and Pivots more thoroughly in the Anchors and Pivot section.

矩形变换编辑模式

Rect Transform edit modes

有两种不同的编辑模式可供选择在Rect Transform组件中,您可以看到蓝图模式和 Raw Edit 模式,分别由以下图标表示:

There are two different edit modes available to you within the Rect Transform component—Blueprint mode and Raw Edit mode—as represented by the following icons, respectively:

图 6.25:矩形变换编辑模式

图 6.25:矩形变换编辑模式

Figure 6.25: Rect Transform edit modes

蓝图模式将忽略对其应用的任何局部旋转或缩放,并将矩形变换边界框显示为未旋转、未缩放的框。以下屏幕截图显示了在关闭和打开蓝图模式的情况下旋转和缩放的面板边界框:

The Blueprint mode will ignore any local rotation or scaling applied to it and will display the Rect Transform bounding box as a non-rotated, non-scaled box. The following screenshot shows the bounding box of a Panel that has been rotated and scaled with Blueprint mode turned off and turned on:

图 6.26:蓝图模式开启与蓝图模式关闭

图 6.26:蓝图模式开启与蓝图模式关闭

Figure 6.26: Blueprint mode on vs. Blueprint mode off

原始编辑模式将允许您更改 UI 对象的锚点和枢轴点,而无需对象移动或根据您所做的更改进行扩展。

The Raw Edit mode will allow you to change the anchor and pivot points of a UI object without the object moving or scaling based on the changes you have made.

锚点和枢轴点

Anchor and Pivot Points

每个 UI 对象都有锚点和枢轴点。当一起使用时,它们将有助于确保您的 UI 定位正确,并在游戏的分辨率或宽高比发生变化时适当缩放。

Every UI object has Anchor Handles and a Pivot Point. When used together, they will help ensure that your UI is positioned appropriately and scales appropriately if the resolution or aspect ratio of your game changes.

锚柄是用四个X形的三角形表示,如下图所示:

The Anchor Handles are represented by four triangles in the form of an X, as shown in the following diagram:

图 6.27:锚柄和枢轴点

图 6.27:锚柄和枢轴点

Figure 6.27: Anchor Handles and Pivot Points

锚可以位于一组组成一个 Anchor,如上图所示图中,也可以拆分成多个Anchor,如下:

The Anchors can be in a group together forming a single Anchor, as shown in the preceding diagram, or they can be split into multiple Anchors, as follows:

图 6.28:拆分锚柄

图 6.28:拆分锚柄

Figure 6.28: Splitting Anchor Handles

锚点将始终形成一个矩形。因此,边将始终对齐。

The Anchors will always form a rectangle. So, the sides will always line up.

矩形变换具有锚点最小值和锚点最大值属性。这些属性以百分比形式表示锚点手柄相对于父级矩形变换的位置。例如,x值中的0将手柄一直移到左边,1将移动一直向右移动手柄。从以下屏幕截图中,您可以看到如何调整x值将锚点相对于父级左右移动:

The Rect Transform has properties for Anchor Min and Anchor Max points. These represent the position of the Anchor Handles relative to the parent’s Rect Transform as percentages. For example, a 0 in an x value moves the handles all the way to the left, and a 1 moves the handles all the way to the right. You can see from the following screenshots how adjusting the x value will move the anchor left and right relative to the parent:

图 6.29:调整锚点的最小值和最大值

图 6.29:调整锚点的最小值和最大值

Figure 6.29: Adjusting Anchor Min and Max

矩形变换具有锚点预设和锚点 最小点最大点的属性。锚点表示 UI 元素与其父级的 Rect Transform 的连接点

The Rect Transform has properties for Anchor Presets and Anchors Min and Max points. The Anchor represents the point at which the UI element is connected to its parent’s Rect Transform:

图 6.30:访问锚点预设

图 6.30:访问锚点预设

Figure 6.30: Accessing the Anchor Presets

由于 Canvas 没有父级,因此您会看到 Anchor Preset 区域为空。无论 Canvas 是什么,情况都是如此选择渲染模式:

As a Canvas has no parent, you’ll see that the Anchor Preset area is empty. This is true regardless of the Canvas Render Mode chosen:

图 6.31:画布游戏对象上缺少锚点预设

图 6.31:画布游戏对象上缺少锚点预设

Figure 6.31: Lack of Anchor Presets on a Canvas GameObject

单击“Anchor Presets”框将显示所有可能的“Anchor Presets”的列表,如以下屏幕截图所示:

Clicking on the Anchor Presets box will display a list of all the possible Anchor Presets, as shown in the following screenshot:

图 6.32:所有可用的 Anchor Presents

图 6.32:所有可用的 Anchor Presents

Figure 6.32: All available Anchor Presents

如果你点击其中一个预设,它会将锚点移动到屏幕截图。您还可以使用锚点预设来调整位置和枢轴点

If you click on one of the presets, it will move the anchors to the position displayed in the screenshot. You can also adjust the position and pivot point using the anchor preset.

如果按住Shift和/或Alt,表示预设的图像将会改变。按住Shift将显示以蓝点表示的枢轴点的位置,按住Alt将显示位置的变化方式,按住两者将显示枢轴点和位置变化。

The images representing the presets will change if you hold down Shift and/or Alt. Holding Shift will show the positions for the pivot point represented by blue dots, holding Alt will show how the position will change, and holding both will show the pivot point and the position change.

图 6.33:设置枢轴和位置

图 6.33:设置枢轴和位置

Figure 6.33: Setting pivot and position

笔记

Note

如果使用 Mac,由于没有Alt键,您将改用Option键。但是,说明仍会在编辑器中显示Alt,并且 Mac 版本不会发生改变。之前的屏幕截图是在 Mac 上拍摄的,尽管它没有Alt键。

If using a Mac, since there is no Alt key, you will use the Option key instead. However, the instructions will still say Alt in the Editor and this does not change for the Mac version. The previous screenshots were taken on a Mac, despite it having no Alt key.

现在,让我们看看画布组件

Now, let’s look at the Canvas Group component.

Canvas Group 组件

Canvas Group component

您可以将Canvas Group组件添加到任何 UI 对象。将其附加到 UI 对象将允许您调整使用单个组件来管理对象及其所有子对象,而不必为每个UI 元素调整这些属性。

You can add a Canvas Group component to any UI object. Attaching it to a UI object will let you adjust the specific properties of the object as well as all of its children with a single component rather than having to adjust these properties for each of the UI elements.

您可以通过从 UI对象的检查器中选择添加组件|布局|画布组(您也可以只搜索画布组)将画布组组件添加到任何 UI 对象。

You can add a Canvas Group component to any UI object by selecting Add Component | Layout | Canvas Group (you can also just search for Canvas Group) from the UI object›s Inspector.

图 6.34:画布组组件

图 6.34:画布组组件

Figure 6.34: The Canvas Group component

您可以使用Canvas Group组件调整以下属性

You can adjust the following properties using a Canvas Group component:

  • Alpha:这是 Canvas Group 内 UI 对象的透明度。该数字介于01之间,表示不透明度的百分比;0表示完全透明,1表示完全不透明。
  • Alpha: This is the transparency of the UI objects within the Canvas Group. The number is between 0 and 1 and represents a percentage of opaqueness; 0 is completely transparent, while 1 is completely opaque.
  • 可交互:此设置确定组内的对象是否可以接受输入。
  • Interactable: This setting determines whether or not the objects within the group can accept input.
  • 阻止射线投射:此设置确定组内的对象是否阻止射线投射击中其后方的物体。
  • Blocks Raycasts: This setting determines if the objects within the group will block raycasts from hitting things behind them.
  • 忽略父组:如果此Canvas Group组件位于 UI 元素上,而该 UI 元素是另一个具有 Canvas Group 组件的 UI 元素的子元素,则此属性决定此 Canvas Group 是否会覆盖其上方的 Canvas Group。如果选择它,它将覆盖父级的 Canvas Group 属性。
  • Ignore Parent Groups: If this Canvas Group component is on a UI element that is a child of another UI element with a Canvas Group component, this property determines whether this Canvas Group will override the one above it or not. If it is selected, it will override the parent’s Canvas Group properties.

介绍 UI 文本和图像

Introducing UI Text and Image

不使用文本或图像很难制作任何 UI 示例。因此,在介绍布局示例之前,让我们先看看了解 UI Text 和 UI Image GameObjects 的基本属性。UI Text 和 UI Images第 11 章和第 12 章将对此进行更详细的讨论

It’s kind of hard to make any UI examples without using text or images. So, before we cover examples of layouts, let’s first look at the basic properties of the UI Text and UI Image GameObjects. UI Text and UI Images are discussed more thoroughly in Chapter 11 and Chapter 12.

当您使用+ | UI | Text创建新的 Text 对象时,您将看到它有一个Text组件。

When you create a new Text object using + | UI | Text, you will see that it has a Text Component.

图 6.35:文本组件

图 6.35:文本组件

Figure 6.35: The Text component

您可以通过更改显示的文本来更改文本框中的单词。在第 11 章中,我们将仔细研究Text组件的各个属性,但是现在,大多数属性的作用应该是相当明显的

You can change the displayed text by changing the words in the Text box. In Chapter 11, we’ll take a closer look at the individual properties of the Text component, but for now, it should be fairly obvious what most of the properties do.

当您使用+ | UI | Image创建新的 Image 对象时,您将看到它有一个Image组件。

When you create a new Image object using + | UI | Image, you will see that it has an Image component.

图 6.36:图像组件

图 6.36:图像组件

Figure 6.36: The Image component

请记住,面板是本质上是一个图像,但预先填充了一些属性。当你创建图像,但是,没有预填充的属性。

Remember that a Panel is essentially an Image but with a few properties prefilled. When you create an Image, however, there are no prefilled properties.

在本章中,我们将使用“源图像”属性,该属性允许您更改显示的精灵。我们将在第 11 章中介绍其他属性

We’ll work with the Source Image property in this chapter, which allows you to change the displayed sprite. We’ll look at the other properties in Chapter 11.

示例

Examples

现在让我们看一些例子!我们将创建一个基本的抬头显示器( HUD )布局和一个背景图像它可以随屏幕拉伸并以多种分辨率缩放。

Now let’s jump into some examples! We’ll be creating a layout for a basic heads-up-display (HUD) and a background image that stretches with the screen and scales at multiple resolutions.

在我们开始构建 UI 之前,让我们先设置一下项目并引入我们需要的艺术资产。

Before we begin building our UI, let’s set up our project and bring in the art assets we will need.

我们将从设置我们的项目开始:

We’ll begin by setting up our project:

  1. 创建一个新的 Unity 项目并将其命名为Mastering Unity UI Project。以2D 模式创建它。
  2. Create a new Unity Project and name it Mastering Unity UI Project. Create it in the 2D mode.

笔记

Note

我们选择 2D 模式,因为它将使我们的 UI 精灵导入更加容易。在 2D 模式下,所有图像都导入为精灵(2D 和 UI)图像,而不是像在 3D 模式下那样导入为纹理图像。您可以随时通过导航至编辑|项目设置|编辑器并将模式更改 3D来更改为 3D 模式。

We’re selecting 2D Mode because it will make importing our UI sprites a lot easier. When in 2D Mode, all images import as Sprite (2D and UI) images rather than Texture images, as they do in 3D Mode. You can change to 3D Mode at any time by navigating to Edit | Project Settings | Editor and changing Mode to 3D.

  1. 在Assets文件夹中创建两个新文件夹,分别名为Scripts Sprites
  2. Create two new folders within the Assets folder named Scripts and Sprites.
  3. 创建一个新场景并将其命名为Chapter6.unity;确保将其保存在Scenes文件夹中。您也可以将SampleScene.unity重命名 Chapter6.unity

    我们将使用艺术资产我根据以下站点上的免费艺术资产进行了修改:

    在文本源文件的Chapter2/Sprites文件夹中,找到catSprites.pngpinkBackground.pnguiElements.png图像,然后将它们拖到Sprites文件夹中导入到项目中

  4. Create a new scene and name it Chapter6.unity; ensure that you save it in the Scenes folder. You could also rename SampleScene.unity to Chapter6.unity.

    We’ll be using art assets that I’ve modified from free art assets found at the following sites:

    In the Chapter2/Sprites folder of the text’s source files, locate the catSprites.png, pinkBackground.png, and uiElements.png images and import them into your project by dragging them into the Sprites folder.

图 6.37:导入精灵

图 6.37:导入精灵

Figure 6.37: Importing the sprites

  1. 现在,我们需要将精灵表切分成单个精灵。如果您已经知道如何切分精灵表,请立即对catSprites图像和uiElements图像进行切分,然后继续“布置基本 HUD”部分。如果您不熟悉该过程,请按照以下步骤操作,然后继续执行步骤 5。
  2. Now, we need to slice the sprite sheets into individual sprites. If you already know how to slice sprite sheets, do so now for the catSprites image and the uiElements image and proceed to the Laying out the Basic HUD section. If you are not familiar with the process, follow these steps, and continue to Step 5.
  3. 选择catSprites图像,按住Ctrl 键,然后单击uiElements图像,以便两者都被选中。
  4. Select the catSprites image, hold Ctrl, and click on the uiElements image so that both are selected.
图 6.38:选择两个精灵表

图 6.38:选择两个精灵表

Figure 6.38: Selecting both sprite sheets

  1. 现在,在Inspector中,为Sprite Mode选择Multiple。然后点击Apply。这将导致catSpritesuiElements精灵都被视为精灵表。
    图 6.39:将精灵转换为多精灵模式

    图 6.39:将精灵转换为多精灵模式

    请注意,检查器显示2 Texture 2Ds Import Settings,因为我们选择了两张图像。

  2. Now, in the Inspector, select Multiple for Sprite Mode. Then hit Apply. This will cause both the catSprites and uiElements sprites to be considered sprite sheets.

    Figure 6.39: Converting the sprites to Multiple Sprite Mode

    Note that the Inspector says 2 Texture 2Ds Import Settings because we have selected two images.

  1. 现在选择catSprites图像并使用导入设置面板中的按钮打开Sprite 编辑器
  2. Now select the catSprites image and open the Sprite Editor with the button in the Import Settings Panel.
  3. 打开Sprite 编辑器后,选择“Slice”
  4. With the Sprite Editor open, select Slice.
  5. 现在更改切片属性,使切片类型自动,并将精灵枢轴应用于底部。完成后,点击切片
  6. Now change the slice properties so that the Slice Type is Automatic and the sprite Pivot is applied to the Bottom. Once done, hit Slice.
  7. 现在您应该看到精灵被分成了三个独立的区域。点击“应用”保存更改。
  8. You should now see the sprite broken into three separate regions. Hit Apply to save the changes.
  9. 现在,如果你点击在项目文件夹视图中,catSprites图像上的箭头,您应该看到单个图像:
  10. Now, if you click on the arrow on the catSprites image in the project folder view, you should see the individual images:
图 6.40:拆分后的精灵表

图 6.40:拆分后的精灵表

Figure 6.40: The split sprite sheet

  1. 完成uiElements图像的第 8 步到第 12 步但将枢轴点设置为中心
  2. Complete Steps 8 through 12 for the uiElements image, but set the pivot point to the Center.

现在我们已经设置好了项目和精灵,我们可以开始使用UI 示例了。

Now that we have our project and sprites set up, we can begin with the UI examples.

布置基本HUD

Laying out a basic HUD

我们将制作如下所示的HUD

We will make a HUD that will look like the following:

图 6.41:我们将要开发的 HUD

图 6.41:我们将要开发的 HUD

Figure 6.41: The HUD we will develop

它将在接下来的章节中得到扩展,但目前,它将有一个非常简单的布局,重点关注父子关系以及锚点/枢轴点位置。

It will be expanded upon in the upcoming chapters, but for now, it’ll have a pretty simple layout that will focus on parent–child relationships and anchor/pivot point placement.

要创建上图所示的HUD,请完成以下步骤:

To create the HUD shown in the preceding image, complete the following steps:

  1. 使用+ | UI | Canvas创建一个新的 Canvas
  2. Create a new Canvas using + | UI | Canvas.
  3. 在 Canvas Inspector中,将名称更改为HUD Canvas
  4. In the Canvas Inspector, change the name to HUD Canvas.
  5. 最好在开始向 UI 添加元素之前设置好所有Canvas Scaler组件信息。如果您尝试在之后执行此操作,您可能会发现 UI 元素将开始重新缩放。我们将使用1024 x 768的理想分辨率。如果您查看pinkBackground图像(我们将在下一个示例中应用),它的分辨率为2048 x 15361024 x 768具有与背景图像相同的宽高比。因此,将您的Canvas Scaler组件设置为以下设置:
    图 6.42:画布缩放器属性

    图 6.42:画布缩放器属性

    我们已将屏幕匹配模式设置为匹配宽度或高度,并将匹配设置设置为1,以便它在垂直方向上保持比例。如果您还记得“按屏幕尺寸缩放”部分的内容,我发现这对大多数使用横向分辨率制作的游戏最有效

  6. It is best to set up all of your Canvas Scaler component information before you even begin adding elements to the UI. If you try to do it afterward, you may find that your UI elements will start rescaling. We›ll work with an ideal resolution of 1024 x 768. If you look at the pinkBackground image (that we’ll apply in the next example), it has a resolution of 2048 x 1536; 1024 x 768 has the same aspect ratio as the background image. So, set your Canvas Scaler component to the following settings:

    Figure 6.42: The Canvas Scaler properties

    We have set the Screen Match Mode to Match Width Or Height, with the Match settings set to 1 so that it maintains the ratios in the vertical direction. If you remember from the Scale with Screen Size section, I find that this works best for most games made with a landscape resolution.

  1. 设置你的游戏视图设置为1024 x 768,这样您就可以看到所有内容都经过适当缩放(有关添加您自己的游戏视图分辨率的说明,请参阅第 1 章的“更改游戏视图的纵横比和分辨率”部分。)
  2. Set your Game view to 1024 x 768 so that you will see everything scaled appropriately (refer to the Changing the Aspect Ratio and Resolution of the Game View section of Chapter 1 for directions on adding your own Game view resolution.)
图 6.43:1024 x 768 游戏视图分辨率

图 6.43:1024 x 768 游戏视图分辨率

Figure 6.43: 1024 x 768 Game view resolution

  1. 由于我们只有一个 Canvas,当我们将任何新的 UI 元素添加到场景中时,它们将自动成为HUD Canvas的子元素。使用+ | UI | Panel创建一个新的 Panel ,并将其重命名为HUD Panel。您将看到它是HUD Canvas的子元素。此 Panel 将表示包含所有 HUD 元素的矩形
  2. Since we only have one Canvas, when we add any new UI elements to our scene, they will automatically be made children of our HUD Canvas. Create a new Panel using + | UI | Panel, and rename it HUD Panel. You will see that it is a child of the HUD Canvas. This Panel will represent the rectangle that holds all the HUD elements.
  3. 点击锚点预设图标打开锚点预设。按住Shift + Alt 键,选择左上角的锚点预设。
  4. Click on the Anchor Presets icon to open the Anchor Presets. Select the top-left Anchor Preset while holding down Shift + Alt.
图 6.44:设置HUD面板的锚点预设

图 6.44:设置HUD面板的锚点预设

Figure 6.44: Set the Anchor Preset of the HUD Panel

  1. 通过选择右侧的箭头展开uiElements图像。找到uiElements_1子图像:
  2. Expand the uiElements image by selecting the arrow on its right. Locate the uiElements_1 sub-image:
图 6.45:设置HUD面板的锚点预设

图 6.45:设置HUD面板的锚点预设

Figure 6.45: Set the Anchor Preset of the HUD Panel

  1. uiElements_1拖到图像槽中HUD 面板上的图像组件
  2. Drag uiElements_1 to the Source Image slot of the Image component on the HUD Panel:
图 6.46:分配了 uiElements_1 的图像组件

图 6.46:分配了 uiElements_1 的图像组件

Figure 6.46: The Image component with uiElements_1 assigned

  1. 目前,面板非常暗淡,并延伸到整个屏幕。让我们通过增加不透明度使其更容易看到。单击图像组件的颜色槽中的白色矩形以打开颜色选择器。将Alpha滑块一直移动到右侧或在 alpha值槽中输入值255
  2. Currently, the Panel is very faint and stretches across the whole screen. Let’s make it easier to see by increasing the opacity. Click on the white rectangle in the Color slot of the Image component to open up a color picker. Move the Alpha slider all the way to the right or input the value 255 in the alpha value slot:
图 6.47:将颜色选择器上的 alpha 值调整为完整 alpha

图 6.47:将颜色选择器上的 alpha 值调整为完整 alpha

Figure 6.47: Adjusting the alpha value on the color picker to full alpha

  1. 单击图像组件中保留纵横比设置旁边的复选框。此属性将使图像始终保持原始图像的纵横比图像,即使您将图像的宽度和高度设置为不具有相同纵横比的值。

    面板现在将仅占据场景的顶部:

  2. Click on the checkbox next to the Preserve Aspect setting in the Image component. This property will make the image always maintain the aspect ratio of the original image, even if you set the width and height of the Image to something that does not have the same aspect ratio.

    The Panel will now take up only the top portion of the scene:

图 6.48:保留纵横比后的面板

图 6.48:保留纵横比后的面板

Figure 6.48: The Panel after preserving the aspect ratio

  1. HUD 面板Rect Transform组件中,您会注意到WidthHeight仍然分别设置为1024768。您还可以从Scene视图中更轻松地看到 Rect Transform 扩展到了精灵。因此,该物体比它看起来的要大得多。
  2. From the Rect Transform component of the HUD Panel, you’ll note that the Width and Height are still set to 1024 and 768, respectively. You can also see more easily from the Scene view that the Rect Transform expands past the viewable region of the sprite. So, the object is much larger than it appears to be.
图 6.49:超出可见图像区域的矩形变换

图 6.49:超出可见图像区域的矩形变换

Figure 6.49: The Rect Transform exceeding the visible image area

  1. 让我们重新调整面板的 Rect Transform 的大小,使其符合我们想要的尺寸,并更好地贴合可视图像。将 Width 更改300 Height更改为102。Rect变换在垂直方向上不会完全贴合,但会非常接近。
  2. Let’s rescale the Rect Transform of the Panel so that it matches the size we are looking for and hugs the viewable image better. Change the Width to 300 and the Height to 102. The Rect Transform won’t be a perfectly snug fit in the vertical direction, but it will be pretty close.
图 6.50:重新缩放 HUD 面板的矩形变换

图 6.50:重新缩放 HUD 面板的矩形变换

Figure 6.50: Rescaling the Rect Transform of the HUD Panel

  1. 现在我们已经设置了主面板。由于所有其他图像都将包含在HUD 面板中,我们希望将它们设为HUD 面板的子级。这样,当屏幕重新缩放时,其他图像将保留在HUD 面板“内部” ,并保持其相对于HUD面板的大小。

    让我们从包含猫角色头部的图像开始。右键单击HUD 面板并选择UI |图像。你会看到它是HUD 面板的子项。将其重命名为Character Holder

  2. We now have the main Panel set up. Since all other images will be contained within the HUD Panel, we want to make them children of the HUD Panel. That way, when the screen rescales, the other images will remain “inside” the HUD Panel and will maintain their size relative to the HUD Panel.

    Let’s start with the Image that holds the cat character’s head. Right-click on the HUD Panel and select UI | Image. You’ll see that it is a child of the HUD Panel. Rename it Character Holder.

  3. 将uiElement_6精灵放置在“源图像”槽中,然后选择“保留纵横比”
  4. Place the uiElement_6 sprite in the Source Image slot and select Preserve Aspect.
  5. 由于角色 持有者图像是HUD 面板的子项,因此我们设置的任何锚定都将相对于HUD 面板。选择按住Shift + Alt 键,向左拉伸Anchor Preset 。此外,设置位置和尺寸变量,如下所示:
  6. Since the Character Holder image is a child of the HUD Panel, any anchoring we set will be relative to the HUD Panel. Choose the left-stretch Anchor Preset while holding Shift + Alt. Additionally, set the position and dimension variables as shown:
图 6.51:重新缩放角色支架的矩形变换

图 6.51:重新缩放角色支架的矩形变换

Figure 6.51: Rescaling the Rect Transform of the Character Holder

  1. 让我们添加猫头的图像。我们希望它完全填满角色支架图像所代表的插槽。因此,我们将使它成为角色 支架图像的子项。右键单击角色支架并选择UI |图像。将其名称更改为角色图像
  2. Let’s add the Image for the cat head. We want it to fully fill out the slot represented by the Character Holder image. So, we will make it a child of the Character Holder image. Right-click on Character Holder and select UI | Image. Change its name to Character Image.
  3. 现在将catSprites_0子图像添加到图像组件的源图像中,然后选择“保留纵横比”
  4. Now add the catSprites_0 subimage to the Source Image of the Image component and select Preserve Aspect.
  5. 按住Shift + Alt 键,将Anchor Preset设置为stretch-stretch
    图 6.52:重新缩放字符图像的矩形变换

    图 6.52:重新缩放字符图像的矩形变换

    由于我们确保角色支架矩形变换紧密贴合支架的图像,因此它应该可以使猫的头完美地贴合支架图像,而无需调整任何设置!

    图 6.53:角色图像装入角色支架中

    图 6.53:角色图像装入角色支架中

  6. Set the Anchor Preset to stretch-stretch while holding Shift + Alt:

    Figure 6.52: Rescaling the Rect Transform of the Character Image

    Since we ensured that we have the Rect Transform of the Character Holder fit snuggly around the holder›s image, it should make the cat›s head fit perfectly within the holder image without having to adjust any settings!

    Figure 6.53: The Character Image fitting within the Character Holder

  1. 现在,我们准备开始制作健康条。我们将以与制作角色支架角色类似的方式创建它。右键单击HUD 面板并选择UI |图像。将新的图像重命名为Health Holder
  2. Now, we are ready to start making the health bar. We will create it similarly to the way we made the Character Holder and Character. Right-click on the HUD Panel and select UI | Image. Rename the new Image Health Holder.
  3. 将uiElement_20精灵放置在“源图像”槽中,然后选择“保留纵横比”
  4. Place the uiElement_20 sprite in the Source Image slot and select Preserve Aspect.
  5. 按照下图所示设置Rect Transform属性,并确保在选择Anchor Preset时按住Shift + Alt
  6. Set the Rect Transform properties as shown in the following image, and ensure that you hold Shift + Alt when selecting the Anchor Preset:
图 6.54:Health Holder 的属性

图 6.54:Health Holder 的属性

Figure 6.54: The properties of the Health Holder

  1. 现在我们只剩下健康了栏!就像我们将猫头图像设为Character Holder的子项一样,我们需要将健康栏的图像设为Health Holder的子项。右键单击Health Holder并选择UI | Image。将新图像重命名为Health Bar
  2. Now, all we have left is the health bar! Just as we made the cat head Image a child of the Character Holder, we will need to make the health bar’s Image a child of Health Holder. Right-click on Health Holder and select UI | Image. Rename the new Image Health Bar.
  3. 将uiElement_23图像放置在“源图像”插槽中。这次,我们不会选择“保留纵横比”,因为我们希望图像水平缩放
  4. Place the uiElement_23 image in the Source Image slot. This time, we will not be selecting Preserve Aspect because we want the image to scale this image horizontally.
  5. 按照以下屏幕截图所示设置Rect Transform属性,并确保在选择Anchor Presets时按住Shift + Alt
    图 6.55:Health Holder 的属性

    图 6.55:Health Holder 的属性

    请注意,添加了一些填充,以便您可以看到Health Holder的边缘

  6. Set the Rect Transform properties as shown in the following screenshot, and ensure that you hold Shift + Alt when selecting the Anchor Presets:

    Figure 6.55: The properties of Health Holder

    Note that a little padding was added so that you can see the edges of Health Holder.

  1. 在继续之前,重要的是确保你的 Rect Transform 定位模式设置为Pivot。否则,你将无法在下一步中移动枢轴。
  2. Before proceeding, it is important that your Rect Transform Positioning mode is set to Pivot. Otherwise, you will not be able to move the pivot in the next step.
图 6.56:矩形变换定位模式

图 6.56:矩形变换定位模式

Figure 6.56: The Rect Transform Positioning mode

  1. 我们快完成了!现在,图像的枢轴点就在中心。这意味着如果我们尝试缩放它,它将向中心缩放。但是,我们希望它能够向左缩放。因此,打开 Anchor Presets,在按住Shift的同时,选择middle-left。这将导致只有枢轴点移动。
  2. We’re almost done! Right now, the pivot point of the image is right at the center. This means if we try to scale it, it will scale toward the center. However, we want it to be able to scale toward the left. So, open the Anchor Presets, and while holding Shift only, select middle-left. This will cause only the pivot point to move.
图 6.57:移动枢轴点

图 6.57:移动枢轴点

Figure 6.57: Moving the pivot point

  1. 现在,当我们调整Rect Transform上的Scale X值时,健康条将向左缩放:
  2. Now, when we adjust the Scale X value on the Rect Transform, the health bar will scale toward the left:
图 6.58:调整生命条的比例

图 6.58:调整生命条的比例

Figure 6.58: Adjusting the scale of the health bar

这就是我们的 HUD 示例!尝试将游戏视图的宽高比更改为不同的设置,以便你可以看到面板适当缩放并查看所有保持的对象相对位置。

That’s it for our HUD example! Try changing your Game view’s aspect ratio to different settings so that you can see the Panel scale appropriately and see all the object-relative positions maintained.

如果您在更改游戏的宽高比时 HUD 出现一些异常,请确保您的对象具有正确的父子关系。您的父子关系应如下:

If your HUD is doing some wonky stuff when you change the Game’s aspect ratio, ensure that your objects have the correct parent–child relationship. Your parent–child relationships should be as follows:

图 6.59:层次结构的父子关系

图 6.59:层次结构的父子关系

Figure 6.59: The Hierarchy’s parent–child relationship

另外,检查以确保锚点和枢轴点设置正确。

Also, check to ensure that the anchor and pivot points are set correctly.

放置 2D 游戏背景图像

Placing a 2D game background image

放置背景只要使用适当的 Canvas 属性,绘制随屏幕缩放的图像并不困难。我们将扩展我们的 HUD 示例并在场景中放置背景图像。

Placing a background image that scales with the screen is not too difficult as long as you use the appropriate Canvas properties. We will expand upon our HUD example and place a background image in the scene.

图 6.60:背景图像的结果

图 6.60:背景图像的结果

Figure 6.60: The result of the background image

我们需要确保该背景图像不仅显示在其他 UI 元素后面,还显示在我们可能放置在场景中的任何游戏对象后面。

We’ll need to ensure that this background image doesn’t just display behind other UI elements but also displays behind any game objects we may put in our scene.

要制作显示在所有 UI 元素以及所有游戏元素后面的背景图像,请完成以下步骤:

To make a background image that displays behind all UI elements as well as all game elements, complete the following steps:

  1. 使用+ | UI | Canvas创建新的 Canvas 。我喜欢使用不同的 Canvas 对不同的 UI 元素进行排序,但在这种情况下,需要新的 Canvas 不仅仅出于个人偏好。我们需要新的 Canvas,因为我们需要具有不同渲染模式的 Canvas。此 Canvas 将使用屏幕空间-相机 渲染模式。
  2. Create a new Canvas using + | UI | Canvas. I like to use different Canvases to sort my different UI elements, but the need for a new Canvas stems from more than personal preference in this case. We need a new Canvas because we need a Canvas with a different Render Mode. This Canvas will use the Screen Space-Camera Render Mode.
  3. 在 Canvas Inspector中,将名称更改为Background Canvas
  4. In the Canvas Inspector, change the name to Background Canvas.
  5. 将渲染模式更改为屏幕空间摄像机,并将主摄像机拖入渲染摄像机槽:
  6. Change the Render Mode to Screen Space-Camera and drag the Main Camera into the Render Camera slot:
图 6.61:背景画布的画布组件

图 6.61:背景画布的画布组件

Figure 6.61: The Canvas component of the Background Canvas

  1. 为了确保此 Canvas 出现在游戏中所有其他 UI 元素和所有 2D 精灵的后面,我们需要使用 Sorting Layers。在 Unity Editor 的右上角,您将看到一个标有Layers 的下拉菜单。选择它并选择Edit Layers
  2. To ensure that this Canvas appears behind all other UI elements and all the 2D sprites in the game, we will need to use Sorting Layers. In the top-right corner of the Unity Editor, you will see a dropdown menu labeled Layers. Select it and select Edit Layers:
图 6.62:编辑图层

图 6.62:编辑图层

Figure 6.62: Editing Layers

  1. 扩展图层排序方式选择箭头。通过选择加号添加新的排序层。将此新层命名为Background
    图 6.63:添加背景排序层

    图 6.63:添加背景排序层

    对图层进行排序的原理是,位于此列表顶部的图层将渲染场景中最远的图层。因此,如果您想添加前景图层,请将其添加到 Default 下方。Default所有新精灵都会自动添加到的图层,因此,除非您创建新图层,否则Background图层将位于您创建的任何新精灵的后面。如果您确实创建了新图层,请确保Background图层位于此列表的顶部

  2. Expand Sorting Layers by selecting the arrow. Add a new Sorting Layer by selecting the plus sign. Name this new layer Background.

    Figure 6.63: Adding the Background Sorting Layer

    Sorting layers work so that whichever is on the top of this list will render the furthest back in the scene. So, if you wanted to add a foreground layer, you’d add it below Default. Default is the layer that all new sprites will automatically be added to, so unless you create new layers, the Background layer will be behind any new sprite you create. If you do create new layers, ensure that the Background layers stay on the top of this list.

  1. 重新选择背景画布并将排序层更改 背景
  2. Reselect Background Canvas and now change the Sorting Layer to Background:
图 6.64:设置背景排序层

图 6.64:设置背景排序层

Figure 6.64: Setting the Background Sorting Layer

  1. 现在,我们需要做的就是添加背景图像。右键单击背景画布并选择UI | 图像。将新图像重命名为背景图像
  2. Now, all we need to do is add the background image. Right-click on the Background Canvas and select UI | Image. Rename the new image to Background Image.
  3. 粉色背景精灵放入源图像槽中。这次,我们不会选择保留比例,因为我们希望图像能够像游戏一样挤压和拉伸屏幕调整大小并始终填充场景。
  4. Place the pinkBackground sprite in the Source Image slot. This time, we will not be selecting Preserve Aspect because we want the image to be able to squash and stretch as the game screen resizes and always fills the scene.
  5. 按照图示设置Rect Transform属性,并确保在选择Anchor Presets时按住Shift + Alt
  6. Set the Rect Transform properties as shown and ensure that you hold Shift + Alt when selecting the Anchor Presets:
图 6.65:背景图像设置

图 6.65:背景图像设置

Figure 6.65: The Background Image settings

由于背景画布设置为屏幕空间 - 相机,您可能需要更改视图才能看到它。它将位于主相机视图所在的位置。

Because the Background Canvas is set to Screen Space – Camera, you may have to change your view so that you can see it. It will be where the Main Camera view is.

就是这样!尝试更改调整游戏视图的宽高比,并在自由宽高比模式下调整屏幕大小,以便您可以看到背景图像始终填满屏幕。此外,尝试将一些非 UI 2D 精灵添加到场景中,看看它们如何在背景上渲染。

That’s it! Try changing the Game view’s aspect ratio around and resizing the screen in Free Aspect mode so that you can see the background image always filling the screen. Also, try adding some non-UI 2D sprites to the scene and see how they render on top of the background.

此示例的一个缺点是允许背景图像改变其宽高比。您会发现由于这个原因,图像在某些宽高比下看起来很糟糕。对于以多种宽高比发布的游戏来说,此背景图像不是一个好选择。我选择此图像有两个原因:

One thing that is not ideal about this example is that the background image is being allowed to change its aspect ratio. You’ll see that the image looks pretty bad at some aspect ratios because of this. This background image will not be a good choice for a game that would be released on multiple aspect ratios. I chose this image for two reasons:

  • 您可以看到选择不太依赖纵横比的图像是多么重要。
  • You can see how it’s important to pick an image that doesn’t depend so highly on aspect ratio.
  • 是免费的!
  • It was free!

我强烈建议使用此方法创建背景图像,使用不会明显显示失真的图案

I highly recommend that if you use this method to create a background image, you use one with a pattern that doesn’t so obviously display distortion.

设置基本弹出菜单

Setting up a basic pop-up menu

本章我们将介绍的最后一个例子将使用Canvas Group组件。直到第 8 章开始编程时,我们才能真正看到此组件的实际作用,但现在我们可以打下基础。我们还将通过此示例进行更多布局 UI 的练习。

The last example we will cover in this chapter will utilize the Canvas Group component. We won›t be able to really see this component in action until we start programming in Chapter 8, but we can lay the groundwork now. We’ll also get a bit more practice with laying out UI with this example.

图 6.66:我们将要布局的弹出面板

图 6.66:我们将要布局的弹出面板

Figure 6.66: The pop-up Panel we will lay out

要创建如上图所示的弹出菜单,请完成以下步骤:

To create the pop-up menu shown in the preceding image, complete the following steps:

  1. 使用+ | UI | Canvas创建一个新的 Canvas
  2. Create a new Canvas using + | UI | Canvas.
  3. Canvas Inspector中,将名称更改为Popup Canvas
  4. In the Canvas Inspector, change the name to Popup Canvas.
  5. 我想在此 Canvas 上使用与HUD Canvas相同的Canvas Scaler属性。 我不会重新设置所有内容,而是使用快捷方式并从HUD Canvas复制Canvas Scaler 。 为此,请选择HUD CanvasCanvas Scaler组件右上角的三个点(“kabob”菜单),然后选择“复制组件”
  6. I want to use the same properties for the Canvas Scaler on this Canvas that I used for HUD Canvas. Instead of setting up all that again, I’ll use a shortcut and copy the Canvas Scaler from HUD Canvas. To do so, select the three dots (the “kabob” menu) in the right-hand corner of the Canvas Scaler component on HUD Canvas and select Copy Component.
  7. 现在选择Popup CanvasCanvas Scaler组件右上角的“kabob”菜单,然后选择Paste Component Values
  8. Now select the “kabob” menu in the right-hand corner of the Canvas Scaler component on Popup Canvas and select Paste Component Values.
  9. 我们将添加一个面板,用于容纳所有项目,类似于我们对 HUD 所做的方式。这将确保一切都保持原样。右键单击Popup Canvas并选择UI | Panel。将新面板重命名为Pause Panel
  10. We will add a Panel that will hold all the items, similarly to the way we did with the HUD. This will ensure that everything stays together as it should. Right-click on Popup Canvas and select UI | Panel. Rename the new Panel Pause Panel.
  11. uiElement_32图像放在源图像槽中,赋予它完整的 alpha 值,然后选择保留纵横比
  12. Place the uiElement_32 image in the Source Image slot, give it a full alpha value, and select Preserve Aspect.
  13. 按照以下屏幕截图所示设置Rect Transform属性,并确保在选择Anchor Preset时按住Shift + Alt
  14. Set the Rect Transform properties as shown in the following screenshot, and ensure that you hold Shift + Alt when selecting the Anchor Preset:
图 6.67:暂停面板的属性

图 6.67:暂停面板的属性

Figure 6.67: The properties of Pause Panel

  1. 现在,让我们给出面板顶部有一个漂亮的横幅。右键单击弹出面板并选择UI |面板。将新面板重命名为暂停横幅
  2. Now, let’s give the Panel a nice banner at the top. Right-click on Popup Panel and select UI | Panel. Rename the new Panel Pause Banner.
  3. 将uiElement_27图像放置在“源图像”槽中,然后选择“保留纵横比”
  4. Place the uiElement_27 image in the Source Image slot and select Preserve Aspect.
  5. 按照以下屏幕截图所示设置Rect Transform属性,并确保在选择Anchor Presets时按住Shift + Alt
    图 6.68:暂停横幅的属性

    图 6.68:暂停横幅的属性

    我们将在后面的章节中向该横幅添加文本

  6. Set the Rect Transform properties as shown in the following screenshot, and ensure that you hold Shift + Alt when selecting the Anchor Presets:

    Figure 6.68: The properties of Pause Banner

    We’ll add the text to this banner in a later chapter.

  1. 这一点的要点示例是为了演示Canvas Group组件的使用(并让您多练习一下布局)。您可以将Canvas Group添加到任何 UI 对象。然后, Canvas Group将应用于您应用它的对象的所有子对象。我们最终将在此 Canvas 上放置更多弹出面板,并且我们希望能够分别控制每个面板,因此我将Canvas Group放在暂停面板

    选择暂停面板,然后选择添加组件|布局|画布组(您也可以直接搜索画布组)。

  2. The main point of this example was to demonstrate the use of the Canvas Group component (and to give you a little more layout practice). You can add a Canvas Group to any UI object. The Canvas Group will then be applied to all the children of the object to which you applied it. We will eventually put more pop-up Panels on this Canvas, and we want to be able to control each of them separately, so I will put the Canvas Group on the Pause Panel.

    Select Pause Panel, and then select Add Component | Layout | Canvas Group (you can also just search for Canvas Group).

现在就是这样!在“暂停面板画布组”组件的检查器中更改Alpha值,您将看到,随着更改它,“暂停面板”“暂停横幅”的Alpha 值都会发生变化。这对于您想要隐藏和显示的弹出菜单非常有用,而无需单独编程每个项目。一旦我们花费 使用暂停面板的时间越长,它上面就会包含更多项目,而且我们会很高兴不必对每个部分进行单独编程。

That’s it for now! Change the values of Alpha in the Inspector of the Pause Panel Canvas Group component, and you will see that as you change it, both the Pause Panel and the Pause Banner alpha values change. This is great for pop-up menus you want to hide and show without having to program each item individually. Once we spend more time with Pause Panel, it will have a lot more items on it, and we will be happy that we don’t have to program each piece individually.

概括

Summary

哇!这一章太精彩了!有很多内容需要讲解,因为这一章为本书的其余部分奠定了基础。我们讨论了 Canvas 的概念以及如何在场景中正确定位它。此外,我们还讨论了基本的 UI 面板,以便我们探索在场景中定位 UI 元素的概念。正确设置 Canvas 及其标量是开发 UI 的重要第一步。

Wow! This chapter was intense! There was a lot to cover, as this chapter set the groundwork that will be used throughout the rest of this book. We discussed the concept of a Canvas and how to correctly position it within your scene. Additionally, we discussed the basic UI Panel to allow us to explore the concept of positioning UI elements within a scene. Correctly setting up Canvases and their scalars is an important first step in developing UI.

下一章将介绍如何创建不同的自动布局,让我们能够以网格形式排列 UI。

The next chapter will cover how to create different automatic layouts that will let us line up our UI in grids.

7

7

探索自动布局

Exploring Automatic Layouts

现在我们已经掌握了使用 Rect Transform 和锚点手动定位、缩放和对齐 UI 元素的基础知识,我们可以探索如何使用自动布局。自动布局允许您对 UI 元素进行分组,以便它们可以自动相对于彼此定位。

Now that we have the basics of manually positioning, scaling, and aligning UI elements with the Rect Transform and anchors, we can explore how to use automatic layouts. Automatic layouts allow you to group your UI elements so that they will position automatically relative to each other.

在很多情况下,您会希望 Unity 自动控制 UI 对象的布局。如果您通过代码生成 UI 项目,并且项目数量可能会发生变化,但您仍希望它们正确排列、缩放和定位,则可以使用自动布局。此外,如果您想要完美间隔的 UI 对象,自动布局将帮助您创建这种完美间距,而无需您自己进行任何位置计算。这些自动布局非常适合在网格或列表中对齐的库存系统等。

There are quite a few scenarios in which you will want Unity to automatically control the layout of your UI objects. If you are generating UI items via code and the number of items may change, but you still want them to line up, scale, and position properly, you can use automatic layouts. Also, if you want perfectly spaced UI objects, automatic layouts will help you create this perfect spacing without having to do any position calculating yourself. These automatic layouts work well for things like inventory systems aligned in a grid or list.

在本章中,我们将讨论以下主题:

In this chapter, we will discuss the following topics:

  • 使用布局组组件自动调整一组UI 对象的间距、位置和对齐
  • Using Layout Group components to automatically space, position, and align a group of UI objects
  • 使用 Layout Element 组件、Content Size Fitter 组件和 Aspect Ratio Fitter 组件调整UI 元素的大小
  • Using the Layout Element component, the Content Size Fitter component, and the Aspect Ratio Fitter component to resize UI elements
  • 如何设置水平HUD选择菜单
  • How to set up a horizontal HUD selection menu
  • 如何设置网格库存
  • How to set up a grid inventory

笔记

Note

本节中显示的所有示例都可以在代码包中名为Chapter 07 .unitypackage的 Unity 包中找到。每个示例图像都有一个标题,说明场景中的示例编号。在场景中,每个示例都在其自己的 Canvas 上,并且一些 Canvas 已停用。要在已停用的 Canvas 上查看示例,只需在Inspector中选中Canvas 名称旁边的复选框即可

All the examples shown in this section can be found within the Unity package named Chapter 07.unitypackage, within the code bundle. Each example image has a caption stating the example number within the scene. In the scene, each example is on its own Canvas, and some of the Canvases are deactivated. To view an example on a deactivated Canvas, simply select the checkbox next to the Canvas’ name in the Inspector.

图 7.1:用于启用或禁用 Canvas 示例的复选框

Figure 7.1: The checkbox to enable or disable a Canvas example

让我们探索不同类型的自动布局组。

Let’s explore the different types of Automatic Layout Groups.

技术要求

Technical requirements

您可以在此处找到本章节的相关代码和资产文件https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2007

You can find the relevant codes and asset files of this chapter here: https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2007

电视自动布局组的类型

Types of automatic layout groups

当 UI 对象具有附加到其上的自动布局组组件,其所有子项都会根据布局组件的参数进行对齐、调整大小和定位。自动布局组选项有三种:水平布局组垂直布局组网格布局组

When a UI object has an automatic layout group component attached to it, all of its children will be aligned, resized, and positioned based on the parameters of the layout component. There are three automatic layout group options: Horizontal Layout Group, Vertical Layout Group, and Grid Layout Group.

以下屏幕截图显示了三个面板(由灰色矩形表示),每个面板有六个 UI 图像子项(由黑色矩形表示);第一个面板有一个水平布局组组件,第二个面板有一个垂直布局组组件,第三个面板有一个网格布局组组件:

The following screenshot shows three Panels (represented by gray rectangles), each with six UI Image children (represented by the black rectangles); the first Panel has a Horizontal Layout Group component, the second Panel has a Vertical Layout Group component, and the third Panel has a Grid Layout Group component:

图 7.2:第 7 章场景中的自动布局组示例 1

图 7.2:自动布局组示例 1 i在第 7 章场景中

Figure 7.2: Automatic Layout Groups Example 1 in the Chapter7 scene

从上面的截图中,你可以清楚地看到这三种类型的自动布局组的作用。您可以使用这三种类型的任意组合来创建嵌套的、间距完美的布局,如下所示:

From the preceding screenshot, you can see clearly what the three types of automatic layout groups accomplish. You can use any combination of the three to create nested, perfectly spaced layouts, as follows:

图 7.3:第 7 章场景中的自动布局组示例 2

图 7.3:第 7 章场景中的自动布局组示例 2

Figure 7.3: Automatic Layout Groups Example 2 in the Chapter7 scene

让我们逐一看看单独布局组并探索它们的各种属性。

Let’s look at each of these layout groups individually and explore their various properties.

水平布局组

Horizontal Layout Group

具有水平布局组组件的 UI 对象的所有子对象将自动并排放置。如果您允许水平布局组调整子项的大小,它们将被定位和缩放,以便它们完全在父对象的 Rect Transform 的范围内。填充属性可以是但是,如果您希望它们超出父级Rect Transform 的边界,则可以进行调整。

All the children of a UI object with a Horizontal Layout Group component will be automatically placed side by side. If you allow the Horizontal Layout Group to resize the children, they will be positioned and scaled so that they are fully within the bounds of the parent object’s Rect Transform. Padding properties can be adjusted, however, if you’d like them to go outside the bounds of the parent’s Rect Transform.

子项在层次结构中出现的顺序决定了它们由水平布局组布局的顺序。子项将从左到右布局。层次结构中最顶层的子项将放置在最左侧的位置,层次结构中最底层的子项将放置在最右侧的位置:

The order in which the children appear in the Hierarchy determines the order in which they will be laid out by the Horizontal Layout Group. The children will be laid out from left to right. The topmost child in the Hierarchy will be placed in the leftmost position, and the bottommost child in the Hierarchy will be placed in the rightmost position:

图 7.4:第 7 章场景中的水平布局组示例 1

图 7.4:第 7 章场景中的水平布局组示例 1

Figure 7.4: Horizontal Layout Groups Example 1 in the Chapter7 scene

要将水平布局组组件添加到 UI 对象,请在对象的检查器中选择添加组件|布局|水平布局组。如果单击Padding属性旁边的箭头,您应该会看到以下内容:

To add a Horizontal Layout Group component to a UI object, select Add Component | Layout | Horizontal Layout Group from within the object’s Inspector. If you click on the arrow next to the Padding property, you should see the following:

图 7.5:水平布局组组件

图 7.5:水平布局组组件

Figure 7.5: The Horizontal Layout Group component

让我们探索每一个进一步了解水平布局组组件的属性。

Let’s explore each of the properties of the Horizontal Layout Group component further.

填充

Padding

Padding属性表示父对象的 Rect Transform 边缘周围的填充。正数将使子对象向内移动,负数将使子对象向外移动。

The Padding property represents the padding around the edges of the parent object’s Rect Transform. Positive numbers will move the child objects inward, and negative numbers will move the child objects outward.

图 7.6:第 7 章场景中的水平布局组示例 2

图 7.6:第 7 章场景中的水平布局组示例 2

Figure 7.6: Horizontal Layout Groups Example 2 in the Chapter7 scene

例如,上图显示了三个面板,它们应用了不同的填充值。第一个面板有没有填充,第二个面板的四个边都有正填充,第三个面板的左侧、右侧和底部都有正填充,但顶部有负填充

As an example, the previous screenshot shows three Panels with various padding values applied. The first Panel has no padding, the second Panel has positive padding on all four sides, and the third Panel has positive padding on the left, right, and bottom but negative padding on the top.

间距

Spacing

Spacing属性决定子对象之间的水平间距。如果使用Child Force Expand属性而不使用Control Child Size属性,则这可能会被覆盖,并且子对象可能具有更大的间距g.

The Spacing property determines the horizontal spacing between the child objects. This may be overridden if you use the Child Force Expand property without the Control Child Size property, and the children may have larger spacing.

子对齐

Child Alignment

子对齐属性决定子组将在其中对齐。此属性有九个选项,如下所示:

The Child Alignment property determines where the group of children will be aligned. There are nine options for this property, as shown here:

图 7.7:水平布局组的子对齐选项

图 7.7:水平布局组的子对齐选项

Figure 7.7: The Child Alignment options of the Horizontal Layout Group

例如,下图显示了三个覆盖整个屏幕的重叠面板。这些父面板的矩形变换区域由所选的矩形变换表示。第一个面板具有左上子对齐。其子项由白色方块表示。第二个面板具有中间中心子对齐,其子项为用灰色方块表示。第三个面板具有右下子对齐,其子项用黑色方块表示:

As an example, the following diagram shows three overlapping Panels that fill the screen. The Rect Transform area for these parent Panels is represented by the selected Rect Transform. The first Panel has an Upper Left Child Alignment. Its children are represented by the white squares. The second Panel has a Middle Center Child Alignment, and its children are represented by gray squares. The third Panel has a Lower Right Child Alignment, and its children are represented by black squares:

图 7.8:第 7 章场景中的水平布局组示例 3

图 7.8:第 7 章场景中的水平布局组示例 3

Figure 7.8: Horizontal Layout Group Example 3 in the Chapter7 scene

需要注意的是,子元素对齐属性仅在子元素(以及间距)不完全填充 Rect Transform,如上图所示是。

It is important to note that the Child Alignment property only shows an effect if the children (along with spacing) don’t completely fill in the Rect Transform, as shown in the preceding diagram.

逆向排列

Reverse Arrangement

反向排列属性是一个切换按钮。选择该切换按钮将导致元素按与层次结构中显示的顺序相反的顺序排列。

The Reverse Arrangement property is a toggle. Selecting the toggle will cause the elements to arrange in the reverse order than they appear in the Hierarchy.

控制子对象大小

Control Child Size

控制子尺寸选项允许自动布局会覆盖子对象的当前宽度高度。如果您选中这些复选框而未选中相应的“子强制扩展”复选框,则子对象将不再可见(除非子对象具有指定了首选宽度的布局元素组件)。

The Control Child Size options allow the automatic layout to override the current Width or Height of the child objects. If you select these checkboxes without selecting the corresponding Child Force Expand checkboxes, your child objects will no longer be visible (unless the children have Layout Element components with Preferred Width specified).

如果不设置此属性,则子项可能会在父项的 Rect Transform 之外绘制 - 也就是说,如果存在太多子项。

If you do not set this property, it is possible that the children will draw outside of the parent’s Rect Transform – that is, if too many children exist.

笔记

Note

此属性会更改子对象的 Rect Transforms 的宽度和高度属性。因此,如果您选择它然后取消选择它,子对象将不会恢复到其原始大小。您必须使用编辑|撤消( Ctrl + Z ) 或通过其 Rect Transform 组件手动重置子对象的大小

This property changes the width and height property of the child objects’ Rect Transforms. So, if you select and then deselect it, the children will not go back to their original sizes. You will have to either use Edit | Undo (Ctrl + Z) or manually reset the size of the children via their Rect Transform components.

由于此属性取决于Child Force Expand属性,因此下一节将展示Control Child Size属性的示例化。

Since this property depends on the Child Force Expand property, examples of the Control Child Size property are presented in the next section.

儿童力量 扩大

Child Force Expand

级强制扩展属性将导致子项填充可用空间。如果未选择相应的控件子项大小,则此属性将移动子项,以便它们及其间距填充空间。这可能会覆盖Spacing属性。如果选择了相应的控件子项大小,它将在选定方向上拉伸子项,以便它们及其间距完全填充空间。这将保持Spacing属性。

The Child Force Expand property will cause the children to fill the available space. If the corresponding Control Child Size is not selected, this property will shift the children so that they and their spacing fill the space. This may override the Spacing property. If the corresponding Control Child Size is selected, it will stretch the children in the selected direction so that they and their spacing completely fill the space. This will maintain the Spacing property.

在以下屏幕截图中,所有三个面板都有一个水平布局组组件,该组件具有中左 子对齐方式,并且选择了不同的子控件大小子强制扩展组合。顶部面板仅选择了子强制扩展宽度,中间面板选择了控制子大小宽度子强制扩展宽度,最后一个面板同时选择了控制子大小属性和子强制扩展 属性:

In the following screenshot, all three Panels have a Horizontal Layout Group component with a Middle Left Child Alignment and different combinations of Child Control Size and Child Force Expand selected. The top Panel has only Child Force Expand Width selected, the middle Panel has Control Child Size Width and Child Force Expand Width selected, and the last Panel has both Control Child Size properties selected and both Child Force Expand properties selected:

图 7.9:第 7 章场景中的水平布局组示例 4

图 7.9:第 7 章场景中的水平布局组示例 4

Figure 7.9: Horizontal Layout Group Example 4 in the Chapter7 scene

接下来我们看看使用儿童量表關係。

Next, let’s look at the Use Child Scale properties.

使用子比例

Use Child Scale

使用子比例的属性包括仅在较新版本的 Unity 中可用。选中此属性将告诉布局组在自动布局时是否应考虑子项的比例。

The Use Child Scale properties are only available in recent versions of Unity. Checking this property will tell the Layout Group whether it should consider the scale of the children when automating the layout.

垂直布局组

Vertical Layout Group

垂直布局组组件的工作方式与水平布局组非常相似,并且具有相同的属性,但具有垂直布局组组件的 UI 对象的子对象将自动放在每个上面而不是并排放置

The Vertical Layout Group component works very similarly to the Horizontal Layout Group and has all the same properties, except children of a UI object with a Vertical Layout Group component will be automatically placed on top of each other, rather than side by side.

与水平布局组一样,子项在层次结构中的显示顺序决定了垂直布局组的布局顺序。子项将按照其在层次结构中的显示顺序从上到下进行布局

As with the Horizontal Layout Group, the order in which the children appear in the Hierarchy determines the order in which they will be laid out by the Vertical Layout Group. The children will be laid out from top to bottom in the same order in which they appear in the Hierarchy:

图 7.10:第 7 章场景中的垂直布局组示例

图 7.10:第 7 章场景中的垂直布局组示例

Figure 7.10: Vertical Layout Group Example in the Chapter7 scene

添加垂直布局组将组件添加到 UI 对象,请在对象的检查器中选择添加组件|布局|垂直布局组。如果单击Padding属性旁边的箭头,您应该会看到以下内容:

To add a Vertical Layout Group component to a UI object, select Add Component | Layout | Vertical Layout Group from within the object’s Inspector. If you click on the arrow next to the Padding property, you should see the following:

图 7.11:垂直布局组组件

图 7.11:垂直布局组组件

Figure 7.11: The Vertical Layout Group component

由于垂直布局组组件与水平布局组组件相同,我们不会进一步探讨每个属性。有关每个属性的解释,请参阅水平布局部分。

Since the properties of the Vertical Layout Group component are identical to those of the Horizontal Layout Group, we won’t explore each of the properties further. For an explanation of each of the properties, refer to the Horizontal Layout Group section.

网格布局组

Grid Layout Group

网格布局组组件允许您以网格布局的形式按列和行组织子对象。它工作原理类似于水平和垂直布局但组还具有一些可以操作的属性。

The Grid Layout Group component allows you to organize child objects in columns and rows in (you guessed it) a grid layout. It works similarly to Horizontal and Vertical Layout Groups but has a few more properties that can be manipulated.

要将网格布局组组件添加到 UI 对象,请在对象的检查器中选择添加组件|布局|网格布局组。如果单击Padding属性旁边的箭头,您应该会看到以下内容:

To add a Grid Layout Group component to a UI object, select Add Component | Layout | Grid Layout Group from within the object’s Inspector. If you click on the arrow next to the Padding property, you should see the following:

图 7.12:网格布局组组件

图 7.12:网格布局组组件

Figure 7.12: The Grid Layout Group component

网格布局组的一些属性与其他两个布局组相同,但让我们进一步了解一下仔细查看网格布局组独有的属性成分。

A few of the properties of the Grid Layout Group are the same as the other two Layout Groups, but let’s look more closely at the properties unique to the Grid Layout Group component.

像元大小

Cell Size

与水平和垂直布局组不同,它们通过其 Rect Transform 组件或通过缩放它们以适合父级的 Rect Transform 来确定子级的大小,而网格布局组要求您指定子对象的宽度和高度。您可以通过设置Cell Size属性的XY属性来实现这一点。这将自动将指定的XY大小分别应用于每个子对象的Rect Transform 的WidthHeight属性

Unlike the Horizontal and Vertical Layout Groups, which determine the size of the children either by their Rect Transform component or by scaling them to fit inside the parent’s Rect Transform, the Grid Layout Group requires you to specify the width and height of the child objects. You accomplish this by setting the X and Y properties of the Cell Size property. This will automatically apply the specified X and Y sizes to each of the children’s Width and Height properties of their Rect Transform, respectively.

由于Cell Size属性以及缺少Control Child size 属性,子元素不能保证适合父元素的 Rect Transform。如果子元素过多,它们可能会被绘制在父元素的 Rect Transform 之外。因此,如果您有一个动态填充的网格,该网格在整个游戏过程中可能会发生变化,并且您希望网格始终适合特定区域,那么您必须为溢出情况做好准备。

Due to the Cell Size property and the lack of a Control Child size property, the children are not guaranteed to fit within the parent’s Rect Transform. If too many children exist, it is possible they will be drawn outside the parent’s Rect Transform. So, if you have a grid filling up dynamically that can change throughout the gameplay and want the grid to always fit within a specific area, you will have to prepare for that overflow scenario.

网格布局组允许您指定X 间距Y 间距。X间距是水平间距,Y 间距是垂直间距。这些值不会被其他属性选择覆盖,因为它们可以与水平和垂直布局组一起使用群组。

The Grid Layout Group allows you to specify both an X Spacing and Y Spacing. The X Spacing is the horizontal spacing, and the Y Spacing is the vertical spacing. These values will not be overridden by further property choices as they can be with the Horizontal and Vertical Layout Groups.

起始角和起始轴

Start Corner and Start Axis

起始角属性决定层次结构中第一个子项将被放置的位置。Start Corner属性有四种选择,如下所示:

The Start Corner property determines where the very first child in the Hierarchy will be placed. There are four choices for the Start Corner property, as shown here:

图 7.13:网格布局组组件的起始角选项

图 7.13:网格布局组组件的起始角选项

Figure 7.13: The Start Corner options of a Grid Layout Group component

起始轴属性决定了所有其他子项相对于第一个子项的位置。有两个选项,如下所示:

The Start Axis property determines where all the other children will be placed relative to the first child. There are two options, as follows:

图 7.14:网格布局组组件的起始轴选项

图 7.14:网格布局组组件的起始轴选项

Figure 7.14: The Start Axis options of a Grid Layout Group component

起始轴属性设置为水平意味着子项将从第一个子项开始以水平方式排列。如果将起始角指定为选项之一,子项将从左到右排列。如果将起始角指定为选项之一,子项将从右到左排列。新行填满后,它将继续下一行,并从起始角的同一侧重新开始。如果起始角是上选项之一,行将继续向下排列。如果起始角是下选项之一,行将继续向上排列。

A Start Axis property set to Horizontal means that the children will be laid out, starting with the first child, in a horizontal fashion. If the Start Corner is assigned to one of the Left options, the children will be placed from left to right. If the Start Corner is assigned to one of the Right options, the children will be placed from right to left. Once the new row is filled, it will continue to the next row and will restart on the same side as the Start Corner. If the Start Corner is one of the Upper options, the rows will continue downward. If the Start Corner is one of the Lower options, the rows will continue upward.

以下屏幕截图演示了子项的流动,其中水平起始轴基于不同的起始选项:

The following screenshot demonstrates the flow of the children, with a Horizontal Start Axis based on the different Start Corner options:

图 7.15:第 7 章场景中的网格布局组示例 1

图 7.15:第 7 章场景中的网格布局组示例 1

Figure 7.15: Grid Layout Group Example 1 in the Chapter7 scene

起始属性设置为垂直意味着子项将从第一个子项开始布局,并且然后以垂直方式排列。子项是从上到下还是从下到上排列,与此属性设置为水平时相同,取决​​于起始角的位置。然后,当一列填满时,子项将根据起始角的位置从左到右或从右到左排列

A Start Axis property set to Vertical means that the children will be laid out starting with the first child, and then in a vertical fashion. Whether the children will be placed from top to bottom or from bottom to top is determined in the same way as it is when this property is set to Horizontal, based on the position of the Start Corner. Then, when a column is filled, the children will be placed from left to right or from right to left, based on the position of the Start Corner.

以下屏幕截图演示了子项的流动,其中垂直起始轴基于不同的起始选项:

The following screenshot demonstrates the flow of the children, with a Vertical Start Axis based on the different Start Corner options:

图 7.16:第 7 章场景中的网格布局组示例 2

图 7.16:Cha 中的网格布局组示例 2pter7场景

Figure 7.16: Grid Layout Group Example 2 in the Chapter7 scene

如您所见,起始角起始轴选项可以极大地改变了子对象的显示顺序。

As you can see, the Start Corner and Start Axis options can greatly change the order in which your child objects are displayed.

约束

Constraint

Constraint属性允许你指定网格的行数或列数。有三个选项,如下所示:

The Constraint property allows you to specify the number of rows or columns the grid will have. There are three options, as shown here:

图 7.17:网格布局组组件的约束选项

图 7.17:网格布局组组件的约束选项

Figure 7.17: The Constraint options of a Grid Layout Group component

固定列固定行数属性允许您分别指定列数或行数。如果您选择其中任一选项,则将出现一个新属性“约束数” 。然后,您可以指定所需的列数或行数。当您选择“固定列数”时,行数将是可变的。当您选择“固定行数”时,列数将是可变的。

The Fixed Column Count and Fixed Row Count properties allow you to specify a number of columns or rows, respectively. If you select either of these options, a new property, Constraint Count, will become available. You then specify how many columns or rows you want. When you select Fixed Column Count, the number of rows will be variable. When you select Fixed Row Count, the number of columns will be variable.

灵活选项会根据所选的单元格大小起始轴选项自动为您计算行数和列数。它将开始按照定义的模式布局子项,直到所选轴上没有剩余空间。然后它将继续。无论哪个轴“起始轴”中指定的轴将具有固定数量的子项,而另一个轴将是可变的。因此,例如,如果“起始轴”设置为“水平”,并且三个子项可以在定义的空间内水平放置,则将有三列,而行数将由总共有多少个子项决定。

The Flexible option automatically calculates the number of rows and columns for you, based on the Cell Size and the Start Axis options chosen. It will begin laying out the children in the defined pattern until there is no space left on the chosen axis. It will then continue. Whichever axis is specified in Start Axis will have a fixed amount of children, and the other axis will be variable. So, for example, if Start Axis is set to Horizontal and three children can fit horizontally within the defined space, there will be three columns, and the number of rows will be determined by how many total children there are.

现在我们已经探索了三个自动布局组,让我们看一个组件,它可以让我们改变这些布局组中子元素的大小或定位。

Now that we’ve explored the three Automatic Layout Groups, let’s look at a component that will let us change the way the children within these layout groups will be sized or positioned.

布局元素

Layout Element

如果使用自动布局调整对象大小,则布局元素组件允许我们指定对象的大小值范围。如果如果父对象尝试调整其尺寸超出这些偏好设置,则布局元素将覆盖从父对象发送的任何尺寸信息。

The Layout Element component allows us to specify a range of size values of an object if it is being sized with an automatic layout. If the parent object tries to size it outside of these preferences, the Layout Element will override any sizing information being sent from the parent object.

要将布局元素组件添加到 UI 对象,请在对象的检查器中选择添加组件|布局|布局元素布局元素具有以下属性:

To add a Layout Element component to a UI object, select Add Component | Layout | Layout Element from within the object’s Inspector. The Layout Element has the following properties:

图 7.18:布局元素组件

图 7.18:布局元素组件

Figure 7.18: The Layout Element component

要使用这些属性,首先要选中它们的复选框以启用它们;复选框将变为可用,以便您可以输入你想要的值:

To use these properties, you first select their checkboxes to enable them; boxes will become available so that you can enter your desired values:

图 7.19:设置布局元素组件的属性

图 7.19:设置布局元素的属性换货组件

Figure 7.19: Setting the properties of the Layout Element component

让我们回顾一下布局元素组件的各个属性将如何影响它们所添加的元素。

Let’s review how the individual properties of the Layout Element component will affect the elements to which they are added.

忽略布局

Ignore Layout

忽略布局属性可用于使子对象忽略其父对象的任何自动布局组件。子对象选择此属性后,可以自由移动和调整大小,并且所有其他子项都将被布局,而不考虑被忽略的子项。

The Ignore Layout property can be used to make child objects ignore any automatic layout component of its parent object. A child with this property selected can be moved and resized freely, and all other children will be laid out without regard for the ignored child.

在以下示例中,Panel 有一个水平布局组组件和五个子对象。第一个子对象标记为 1,它有一个布局元素组件,并且已选择“忽略布局” 属性:

In the following example, the Panel has a Horizontal Layout Group component and five child objects. The first child, labeled with a 1, has a Layout Element component with the Ignore Layout property selected:

图 7.20:第 7 章场景中的布局元素示例 1

图 7.20:第 7 章场景中的布局元素示例 1

Figure 7.20: Layout Element Example 1 in the Chapter7 scene

您可以看到,由于为第一个子项选择了“忽略布局”属性,因此可以将其移到父面板,在确定其他子面板的位置和比例时,它会被忽略。它还保持了其原始的 Rect Transform 比例。

You can see that since the Ignore Layout property is selected for the first child; it can be moved around outside of the parent Panel, and it was ignored when the position and scale of the other children were determined. It also maintained its original Rect Transform scale.

如果取消选择“忽略布局”属性,则第一个子项将添加到水平布局组中,并且其他孩子。

If the Ignore Layout property is deselected, the first child will be added to the Horizontal Layout Group with the other children.

宽度和高度属性

The Width and Height properties

布局元素组件有三组属性,可用于指定对象调整大小的方式。这些如果分配的尺寸超出提供的值,则属性将覆盖父对象分配给子对象的尺寸。

The Layout Element component has three sets of properties that can be used to specify the way you want an object to resize. These properties will override the size being assigned to the child by the parent object if the assigned size is outside of the provided values.

笔记

Note

需要注意的是,这些属性不会覆盖网格布局组组件的单元格大小设置。它们对 Gr 中的子项没有影响id布局组。

It is important to note that these properties will not override the Cell Size settings of the Grid Layout Group component. They will have no effect on a child within a Grid Layout Group.

最小宽度和高度

Min Width and Height

Min Width和Min Height属性是子对象可以达到的最小宽度和高度。如果父对象缩放向下,子项将缩小,直到达到其最小宽度最小高度。一旦达到,它将不再沿该方向缩放。

The Min Width and Min Height properties are the minimum width and height a child object can achieve. If the parent object is scaled down, the child will scale down until it meets its Min Width or Min Height. Once it does so, it will no longer scale in that direction.

在下图中,Panel 有一个水平布局组组件和五个子对象。第一个子对象标记为 1,它有一个布局元素组件,并且设置了最小宽度最小高度 属性:

In the following diagram, the Panel has a Horizontal Layout Group component and five child objects. The first child, labeled with a 1, has a Layout Element component with the Min Width and Min Height properties set:

图 7.21:第 7 章场景中的布局元素示例 2

图 7.21:第 7 章场景中的布局元素示例 2

Figure 7.21: Layout Element Example 2 in the Chapter7 scene

您可以看到,父对象的水平布局组在缩小自身时尝试将所有子对象也缩小。其他四个子对象也进行了缩放,但由于第一个子对象设置了最小宽度最小高度属性,因此它拒绝缩放拥有任何进一步的权利。

You can see that the parent object’s Horizontal Layout Group tried to scale all the children down with it as it scaled down itself. The other four children scaled, but since the first child had a Min Width and Min Height properties set, it refused to scale down any further.

首选宽度和首选高度

Preferred Width and Preferred Height

首选宽度和首选高度属性有点令人困惑,因为它们的表现不同,具体取决于您对父布局组的设置。没有官方的最大宽度最大高度设置,尽管有最小宽度最小高度设置。但是,首选宽度首选高度属性可用于指定子对象将达到的最大尺寸,但前提是选择了父布局组的正确设置

The Preferred Width and Preferred Height properties are a little confusing because they perform differently, depending on the settings you have for the parent’s layout group. There is no official Max Width and Max Height setting, despite there being a Min Width and Min Height setting. The Preferred Width and Preferred Height properties, however, can be used to specify the maximum size the child object will achieve, but only if the correct settings on the parent’s layout group are selected.

下图包含三个带有垂直布局组组件和各种设置的面板。它们的子级在布局元素组件中也有各种首选高度设置

The following diagram contains three Panels with Vertical Layout Group components and various settings. Their children also have various settings for Preferred Height within a Layout Element component:

图 7.22:第 7 章场景中的布局元素示例 3

图 7.22:第 7 章场景中的布局元素示例 3

Figure 7.22: Layout Element Example 3 in the Chapter7 scene

第一个父面板有一个垂直布局组组件,其中选中了控制子尺寸的宽度高度,以及子强制扩展宽度高度。它的所有子项均未在布局元素组件中设置首选宽度首选高度。比较其他面板时,第一个面板将作为默认参考。

The first parent Panel has a Vertical Layout Group component with Control Child Size Width and Height selected, as well as Child Force Expand’s Width and Height. None of its children have a Preferred Width or Preferred Height setting within a Layout Element component. The first Panel will act as the default for reference when comparing the others.

第二个父面板具有与第一个相同的属性 - 一个垂直布局组组件,其中控制子尺寸宽度高度被选中,以及子强制扩展 宽度高度。但是,它的第一个子组件在布局元素组件中具有首选高度设置为100

The second parent Panel has the same properties as the first – a Vertical Layout Group component with Control Child Size’s Width and Height selected, as well as Child Force Expand Width and Height. However, its first child has a Preferred Height setting of 100 within a Layout Element component.

你可以看到,由于第二个父面板选择了Child Force Expand高度,第一个子面板的Layout Element组件中的Preferred Height100,导致第一个子面板比其他四个子项高出整整100 个单位。因此,当在父项上选择“子项强制扩展”属性时,具有“首选高度”的子项将不会使用“首选高度”作为其最大可能高度;它会将该值添加到父项的布局组组件分配的高度。

You can see that because the second parent Panel has Child Force Expand’s Height selected, the Preferred Height of 100 in the Layout Element component of the first child causes the first child to be exactly 100 units taller than the other four children. So, when the Child Force Expand property is selected on the parent, the child with the Preferred Height will not use Preferred Height as its maximum possible height; it will add that value to the height assigned by the parent’s layout group component.

第三个面板有一个垂直布局组组件,其中选择了控制子尺寸 宽度高度,以及子强制扩展宽度。它没有像其他两个一样选择子强制扩展高度。它的所有子项在布局元素组件中都将首选高度设置为100

The third Panel has a Vertical Layout Group component with Control Child Size Width and Height selected, as well as Child Force Expand’s Width. It does not have Child Force Expand’s Height selected as the other two do. All of its children have a Preferred Height setting of 100 within a Layout Element component.

如果将第三个面板中的子项与第一个面板(默认)中的子项进行比较,您会发现子项较矮。这是因为它们的首选高度设置为小于垂直布局组组件尝试分配给它们的高度。因此,当取消选择子项强制扩展Height属性时,子项将以预期的方式使用其首选高度设置 - 使其成为子项应达到的最大尺寸

If you compare the children in the third Panel to the children in the first Panel (the default), you can see that the children are shorter. This is because their Preferred Height is set to a smaller number than the height that the Vertical Layout Group component attempts to assign to them. So, when the Child Force Expand’s Height property is deselected, the children will use their Preferred Height setting in the expected way – making it the maximum size the children should attain.

因此,如果您希望“首选宽度”“首选高度”设置作为可达到的最大宽度或高度,则需要取消选择相应的“子强制扩展”属性父对象

Therefore, if you want the Preferred Width or Preferred Height settings to work as a maximum attainable width or height, you will need to deselect the corresponding Child Force Expand property on the parent object.

灵活的宽度和灵活的高度

Flexible Width and Flexible Height

Flexible WidthFlexible Height属性表示百分比,其中百分比是子元素相对的大小给其他子项。由于这些值是百分比,因此 0 表示 0%,1 表示100%。

The Flexible Width and Flexible Height properties represent a percentage, where the percentage is the size of the child relative to the other children. Since these values are percentages, a value of 0 would represent 0% and a value of 1 would represent 100%.

Preferred WidthPreferred Height一样,除非取消选择Child Force Expand属性,否则此设置不会按预期工作。在下面的示例中,两个面板和子面板具有几乎相同的设置。两者之间的唯一区别是顶部父面板选择了Child Force ExpandWidth属性,而底部父面板没有。因此,您可以看到,如果在父面板上选择了Child Force ExpandWidth,则子面板的Flexible Width设置的值将被忽略:

As with Preferred Width and Preferred Height, this setting doesn’t work as expected unless the Child Force Expand property is deselected. In the following example, the two Panels and children have nearly identical settings. The only difference between the two is that the top parent Panel has Child Force Expand’s Width property selected and the bottom parent Panel does not. So, you can see that the values set for Flexible Width of the children are ignored if Child Force Expand’s Width is selected on the parent:

图 7.23:第 7 章场景中的布局元素示例 4

图 7.23:第 7 章场景中的布局元素示例 4

Figure 7.23: Layout Element Example 4 in the Chapter7 scene

第二个孩子上图的行从左到右具有以下Flexible Width设置: 00.50.7511.5。您可以看到子项已根据百分比相互缩放。第一个子项不可见,因为它的Flexible Width 0

The children in the second row of the preceding figure have the following Flexible Width settings from left to right: 0, 0.5, 0.75, 1, and 1.5. You can see that the children have scaled relative to each other based on the percentages. The first child is not visible because it has a Flexible Width of 0.

布局元素组件本质上允许我们覆盖元素的自动大小和位置。现在,让我们回顾一下允许我们自动覆盖元素的某些组件y 尺寸UI 元素。

The Layout Element component essentially lets us override the automatic size and position of an element. Now, let’s review some components that will allow us to automatically size UI elements.

装配工

Fitters

有两个 fitter 布局组件。这些组件使其所附着的对象的 Rect Transform 适合指定区域内。

There are two fitter layout components. These components make the Rect Transform of the object on which they are attached fit within a specified area.

内容尺寸适配器

Content Size Fitter

Content Size Fitter组件允许你强制父级的尺寸适应其子级的尺寸。此适配可以基于孩子的最小尺寸或首选尺寸。

The Content Size Fitter component allows you to force the size of the parent to fit around the size of its children. This fitting can be based on the minimum or preferred size of the children.

要将Content Size Fitter组件添加到 UI 对象,请在对象的检查器中选择添加组件|布局| Content Size Fitter。Content Size Fitter组件具有以下属性:

To add a Content Size Fitter component to a UI object, select Add Component | Layout | Content Size Fitter from within the object’s Inspector. The Content Size Fitter component has the following properties:

图 7.24:Content Size Fitter 组件

图 7.24:Content Size Fitter 组件

Figure 7.24: The Content Size Fitter component

您可以为水平适配垂直适配选择以下属性

You can choose the following properties for the Horizontal Fit and the Vertical Fit:

图 7.25:Content Size Fitter 组件可能的适配选项

图 7.25:Content Size Fitter 组件可能的适配选项

Figure 7.25: The possible fit options of the Content Size Fitter component

如果选择了“不受约束”属性, Content Size Fitter将不会沿该轴调整对象的大小

If the Unconstrained property is selected, Content Size Fitter will not adjust the size of the object along that axis.

如果选择了Min Size属性, Content Size Fitter将根据子元素的最小尺寸调整对象的尺寸。此最小尺寸由子元素Layout Element组件的Min WidthMin Height属性决定。

If the Min Size property is selected, the Content Size Fitter will adjust the size of the object based on the minimum size of the children. This minimum size is determined by the Min Width and Min Height properties of the Layout Element component of the children.

如果父级具有网格布局组组件,则子级不必具有布局元素组件,此属性才有效。如果为具有网格布局组组件的对象选择了此属性,则父级的 Rect Transform 将根据单元格大小填充属性拥抱子级,如下图所示

The children do not have to have a Layout Element component if the parent has a Grid Layout Group component for this property to work. If this property is selected for an object with a Grid Layout Group component, the Rect Transform of the parent will hug the children based on the Cell Size and Padding properties, as shown in the following diagram:

图 7.26:第 7 章场景中的 Content Size Fitter 示例

图 7.26:第 7 章场景中的 Content Size Fitter 示例

Figure 7.26: The Content Size Fitter Example in the Chapter7 scene

如果首选大小属性为选中后,Content Size Fitter将根据子项的首选大小调整对象的大小。此首选大小由子项的Layout Element组件的Preferred WidthPreferred Height属性决定。如果对象具有Grid Layout Group组件,则此设置将以完全相同的方式执行我将其视为最小尺寸

If the Preferred Size property is selected, the Content Size Fitter will adjust the size of the object based on the preferred size of the children. This preferred size is determined by the Preferred Width and Preferred Height properties of the Layout Element component of the children. If the object has a Grid Layout Group component, this setting will perform in the exact same way as Min Size.

宽高比调整器

Aspect Ratio Fitter

纵横比适配组件的工作原理类似于布局元素组件,因为它允许您覆盖发送给它的尺寸限制。它将强制其所连接的 UI 对象根据纵横比调整大小。

The Aspect Ratio Fitter component works similarly to the Layout Element component, as it allows you to override the size constraints being sent to it. It will force the UI object on which it is attached to resize based on an aspect ratio.

要将Aspect Ratio Fitter组件添加到 UI 对象,请在对象的Inspector中选择Add Component | Layout | Aspect Ratio Fitter (Script)。Aspect Ratio Fitter组件具有以下属性:

To add an Aspect Ratio Fitter component to a UI object, select Add Component | Layout | Aspect Ratio Fitter (Script) from within the object’s Inspector. The Aspect Ratio Fitter component has the following properties:

图 7.27:Aspect Ratio Fitter 组件

图 7.27:Aspect Ratio Fitter 组件

Figure 7.27: The Aspect Ratio Fitter component

一旦选择了“纵横比”选项,纵横比属性将处于可编辑状态。纵横比属性定义了 Rect Transform 将保持的纵横比。例如,如果您想要纵横比为 4:3,您只需在框中输入 4/3,它就会将其转换为十进制值:

Once you select an Aspect Mode option, the Aspect Ratio property will be editable. The Aspect Ratio property defines the aspect ratio that the Rect Transform will maintain. For example, if you want an aspect ratio of 4:3, you can simply enter 4/3 in the box, and it will convert it to the decimal value:

图 7.28:将分数输入到纵横比拟合器组件中

图 7.28:将分数输入到纵横比拟合器组件中

Figure 7.28: Entering fractions into an Aspect Ratio Fitter Component

您可以为“纵横比模式”属性选择以下属性

You can choose the following properties for the Aspect Mode property:

图 7.29:Aspect Ratio Fitter 组件的 Aspect Mode 选项

图 7.29:Aspect Ratio Fitter 组件的 Aspect Mode 选项

Figure 7.29: The Aspect Mode options for the Aspect Ratio Fitter component

如果选择了None属性,则纵横比适配器将不会调整尺寸以适合纵横比

If the None property is selected, the Aspect Ratio Fitter will not adjust the size to fit within the Aspect Ratio.

如果选择了“宽度控制高度”属性,则纵横比适配器将根据对象的宽度调整高度的大小。

If the Width Controls Height property is selected, the Aspect Ratio Fitter will adjust the size of the height based on the width of the object.

如果选择了“高度控制宽度”属性,则纵横比适配器将根据对象的高度调整宽度的大小。

If the Height Controls Width property is selected, the Aspect Ratio Fitter will adjust the size of the width based on the height of the object.

如果选择了“适合父级”属性,则“长宽比适配器”将调整对象的大小以适合其父对象,但将保持“长宽比”。这将使子对象保持在父级的范围内。

If the Fit In Parent property is selected, the Aspect Ratio Fitter will adjust the size of the object to fit within its parent object but will maintain the Aspect Ratio. This will make the child object stay within the bounds of the parent.

如果选择了Envelope Parent属性, Aspect Ratio Fitter将调整对象的大小以覆盖其父对象,但将保持Aspect Ratio。这类似于Fit In Parent属性,不同之处在于它不是停留在父级的边界内,而是可以超出父级的边界界限。

If the Envelope Parent property is selected, the Aspect Ratio Fitter will adjust the size of the object to cover its parent object but will maintain the Aspect Ratio. This is similar to the Fit In Parent property, except that instead of staying within the bounds of the parent, it can go outside the bounds.

如果您尝试将Aspect Ratio Fitter组件添加到具有布局组组件的父级的子级,您将在子级上看到以下消息:

If you try to add an Aspect Ratio Fitter component to a child with a parent that has a layout group component, you’ll see the following message on the child:

图 7.30:Aspect Ratio Fitter 警告消息

图 7.30:Aspect Ratio Fitter 警告消息

Figure 7.30: The Aspect Ratio Fitter warning message

虽然您可以忽略此消息并继续执行此操作,但这样做并不完全符合预期。建议的解决方法是将Aspect Ratio Fitter组件添加到组内子项的子项中。例如,在下图中,将 Panel 添加为Horizo​​ntal Layout Group的子项。然后,将带有Aspect Ratio Fitter组件的子项添加到 Panel 中,以便子项可以具有 4:3 的宽高比

While you can ignore this message and do it anyway, it doesn’t work entirely as expected. The recommended workaround is to add the Aspect Ratio Fitter component to a child of the child within the group. For example, in the following diagram, a Panel was added as a child of the Horizontal Layout Group. Then, a child with an Aspect Ratio Fitter component was added to the Panel so that the child could have the 4:3 Aspect Ratio:

图 7.31:第 7 章场景中的纵横比适配示例

图 7.31:t 中的纵横比拟合示例第七章场景

Figure 7.31: Aspect Ratio Fitter Example in the Chapter7 scene

现在我们已经了解了各种自动布局组件的所有属性,让我们看一些如何使用它们的示例!

Now that we’ve looked at all the properties of the various automatic layout components, let’s look at some examples of how to use them!

示例

Examples

我们将继续处理第 6 章中创建的场景并使用为其导入的艺术资产。

We’ll continue working on the scene created in Chapter 6 and use the art assets imported for them.

笔记

Note

如果您没有遵循第 6 章中的示例,但想要遵循这些示例,您可以从代码包中下载名为第 07 章- 示例 - Start.unitypackage 的Unity 包

If you did not follow along with the examples in Chapter 6 but would like to follow along with these, you can download the unity package named Chapter 07 - Examples – Start.unitypackage from the code bundle.

除了已经添加到我们的项目中的艺术作品之外,我们还将使用我根据https://opengameart.org/content/platformer-pickups-pack找到的免费艺术资产修改过的艺术资产。

In addition to the art already added to our project, we’ll be using art assets that I’ve modified from free art assets found at https://opengameart.org/content/platformer-pickups-pack.

从上一个链接下载的版本提供了许多单独的图像。我本来可以使用这些图像,但出于性能原因,最好尽可能使用精灵表。因此,您可以在代码包中找到标记为foodSpriteSheet.png 的精灵表。为了将所有图像组合成精灵表,我使用了 Texture Packer 程序,该程序可https://www.codeandweb.com/texturepacker找到。

The download from the previous link provides many individual images. I could have used those, but for performance reasons, it is best to use sprite sheets whenever possible. So, you can find the sprite sheet labeled foodSpriteSheet.png in the code bundle. To combine all the images into a sprite sheet, I used the program Texture Packer, which can be found at https://www.codeandweb.com/texturepacker.

在开始以下示例之前,请完成以下步骤:

Before you begin with the following examples, complete the following steps:

  1. foodSpriteSheet.png精灵表导入到项目的Asset/Sprites文件夹中。
  2. Import the foodSpriteSheet.png sprite sheet into your project’s Asset/Sprites folder.
  3. foodSpriteSheet.pngSprite Mode改为Multiple。使用Sprite Editor自动对Sprite Sheet 进行切片。
  4. Change the Sprite Mode of foodSpriteSheet.png to Multiple. Use the Sprite Editor to automatically slice the sprite sheet.
  5. 自动切片会导致在 Sprite 表中创建一个空白图像。在Sprite 编辑器中找到以下屏幕截图中显示的矩形,然后选择并删除它:
  6. Automatic slicing results in a blank image being created in the sprite sheet. Find the rectangle shown in the following screenshot within the Sprite Editor, and then select and delete it:
图 7.32:需要移除的空精灵

图 7.32:需要移除的空精灵

Figure 7.32: An empty sprite that needs removing

  1. 应用更改后,您你的Sprites文件夹中应该包含以下内容
  2. Once you apply your changes, you should have the following in your Sprites folder:
图 7.33:当前项目中的所有精灵

图 7.33:当前项目中的所有精灵

Figure 7.33: All sprites currently in the project

  1. Ctrl + D复制名为Chapter6的场景,并将其命名为Chapter7。打开Chapter7场景并完成以下示例 w那个场景里。
  2. Duplicate your scene named Chapter6 by pressing Ctrl + D, and name it Chapter7. Open the Chapter7 scene and complete the following examples within that scene.

现在您已经复制了场景并导入了艺术作品,让我们看看如何应用一些自动布局。

Now that you have the scene duplicated and the art imported, let’s look at applying some automatic layouts.

布局 HUD 选择菜单

Laying out a HUD selection menu

本章中我们将介绍的第一个示例是屏幕右下角的 HUD 选择菜单使用水平布局组组件。完成后,它将如下图所示:

The first example we will cover in this chapter is a HUD selection menu in the lower-right corner of the screen that uses the Horizontal Layout Group component. When we are done, it will look like the following figure:

图 7.34:我们将在此示例中构建的 HUD 选择菜单

图 7.34:我们将在此示例中构建的 HUD 选择菜单

Figure 7.34: The HUD selection menu we will build in this example

要创建如上图所示的 HUD 组,请完成以下步骤:

To create the HUD group shown in the preceding screenshot, complete the following steps:

  1. 目前,我们在屏幕左上角有一个名为HUD Panel 的面板。为了清楚起见,将此面板重命名为Top Left Panel
  2. Currently, we have a Panel in the upper-left corner of the screen named HUD Panel. For clarity’s sake, rename this Panel Top Left Panel.
  3. 我们将创建一个新的 HUD 面板来保存我们的水果库存。我们希望将新的 HUD 面板放在 HUD 画布中。右键单击层次结构中名为HUD 画布的画布,然后选择UI |面板
  4. We will create a new HUD Panel to hold our fruity inventory. We want to put our new HUD Panel in the HUD Canvas. Right-click on the Canvas named HUD Canvas in the Hierarchy and select UI | Panel.
  5. 将新面板重命名右下角面板
  6. Rename the new Panel Bottom Right Panel:
图 7.35:层次结构中的面板

图 7.35:层次结构中的面板

Figure 7.35: The Panels in the Hierarchy

  1. 更改右下角面板的 Rect Transform 属性,使面板固定在右下角,宽度500 高度100 选择右下角锚点预设时,请记住按住Shift + Alt :
    图 7.36:右下方面板的矩形变换

    图 7.36:右下方面板的矩形变换

    您应该看到以下内容在你的游戏视图中:

    图 7.37:生成的面板

    图 7.37:生成的面板

  2. Change the Rect Transform properties of the Bottom Right Panel so that the Panel is anchored in the lower-right corner, has a Width of 500, and has a Height of 100. Remember to hold down Shift + Alt when selecting the lower-right anchor preset:

    Figure 7.36: The Rect Transform of the Bottom Right Panel

    You should see the following in your Game view:

    Figure 7.37: The resulting Panel

  1. 现在,我们将用uiElements.png精灵之一替换图像。将uiElements_1拖到Image组件的Source Image属性中。更改Color属性,使其具有完全不透明度:
    图 7.38:面板的图像元件属性

    图 7.38:面板的图像元件属性

    您现在应该看到在您的游戏视图中执行以下操作:

    图 7.39:生成的面板

    图 7.39:生成的面板

  2. Now, we will replace the image with one of the uiElements.png sprites. Drag uiElements_1 into the Source Image property of the Image component. Change the Color property so that it has full opacity:

    Figure 7.38: The Image component properties of the Panel

    You should now see the following in your Game view:

    Figure 7.39: The resulting Panel

  1. 要创建我们想要的布局,我们需要在右下角面板上添加一个水平布局组组件。选择添加组件|布局|水平布局组。我们将立即调整其属性。首先,让我们给这个面板一些子项,以便我们可以看到属性的效果
  2. To create the layout we want, we need to add a Horizontal Layout Group component to the Bottom Right Panel. Select Add Component | Layout | Horizontal Layout Group. We will adjust its properties momentarily. First, let’s give this Panel some children so that we can see the effects of the properties take place.
  3. 右键单击层次结构中的右下面板,然后选择UI |图像。将此图像重命名为 图像项持有者。我们不会更改此图像的Rect Transform组件,因为我们将允许其父级的水平布局组控制其大小、位置和锚点。
  4. Right-click on the Bottom Right Panel in the Hierarchy and select UI | Image. Rename this Image Item Holder. We won’t be changing the Rect Transform component of this Image because we will allow the Horizontal Layout Group of its parent to control its size, position, and anchor.
  5. 此图像将成为项目的背景容器。因此,将uiElement_6拖到其图像组件的源图像中。
  6. This Image will be the background holder for the item. So, drag uiElement_6 into its Image component’s Source Image.
  7. 现在,让我们添加水果的图像。右键单击层次结构中的项目持有者,然后选择UI |图像为其添加子图像。将此图像重命名为食物
  8. Now, let’s add the Image for the fruit. Right-click on Item Holder in the Hierarchy and select UI | Image to give it a child Image. Rename this Image Food:
图 7.40:UI 元素的层次结构

图 7.40:UI 元素的层次结构

Figure 7.40: The Hierarchy of UI elements

  1. 为了确保我们看到的不只是一块白色方块,让我们用foodSpriteSheet.png中的一个食物精灵替换源图像。我使用了foodSpriteSheet_18,它是一个完整的橙色:
    图 7.41:食物元素的图像组件

    图 7.41:食物元素的图像组件

    你应该看到类似这样的内容:

    图 7.42:结果面板上有橙色

    图 7.42:结果面板上有橙色

    如果您的橙子及其托架与我的不同,请不要担心。当我们开始添加更多子项并调整水平组布局时,所有内容都会弹到正确的位置。

  2. To ensure that we aren’t just looking at a white block, let’s replace the Source Image with one of the food sprites from foodSpriteSheet.png. I’ve used foodSpriteSheet_18, which is a full orange:

    Figure 7.41: The Image component of the Food element

    You should see something that looks like this:

    Figure 7.42: The resulting Panel with an orange

    If your orange and its holder are in a different place than mine, don’t worry. When we start adding more children and adjusting the Horizontal Group Layout, everything should pop into its proper place.

  1. 我们不想我们的橙子 Image 的纵横比会扭曲,而且我们还希望确保它始终填满Item Holder图像而不会超出它。所以,让我们调整一下Rect TransformImage组件上的几个属性。首先,让我们通过将其Rect Transform锚点预设更改为stretch-stretch来确保橙子 Image 始终填满插槽并且不会超出它。这似乎不会有太大变化,但如果我们改变屏幕尺寸,它将帮助我们的橙子正确缩放。此外,为了确保容器边缘和食物之间有一点间隙,请将LeftTopRightBottom属性更改5
  2. We don’t want our orange Image to have its aspect ratio distorted, and we also want to ensure that it always fills the Item Holder image without expanding past it. So, let’s adjust a few properties on the Rect Transform and Image components. First, let’s ensure that the orange Image will always fill the slot and not expand past it by changing its Rect Transform anchor preset to stretch-stretch. This won’t appear to have changed much, but it will help our orange scale properly if we change the screen size. Also, to ensure that there is a little bit of spacing between the edge of the container and the food, change the Left, Top, Right, and Bottom properties to 5.
  3. 现在,在橙子的图像组件上选择“保留方面” 。您的食物图像现在应具有以下属性:
  4. Now, select Preserve Aspect on the Image component of the orange. Your Food Image should now have the following properties:
图 7.43:食物 UI 图像的 Rect Transform 和 Image 组件

图 7.43:食物 UI 图像的 Rect Transform 和 Image 组件

Figure 7.43: The Rect Transform and Image components of the Food UI Image

  1. 现在,我们准备开始添加更多内容子项。在层次结构中选择Item Holder Image ,然后按Ctrl + D四次,这样总共有五个Item Holder GameObjects:
    图 7.44:UI 元素的层次结构

    图 7.44:UI 元素的层次结构

    您应该在游戏视图中看到以下内容

    图 7.45: 生成的面板上有五个橙子

    图 7.45: 生成的面板上有五个橙子

  2. Now, we’re ready to start adding some more children. Select the Item Holder Image in the Hierarchy and press Ctrl + D four times so that there is a total of five Item Holder GameObjects:

    Figure 7.44: The Hierarchy of UI elements

    You should see the following in the Game view:

    Figure 7.45: The resulting Panel with five oranges

  1. 我不喜欢我的对象名称带有带数字的括号,所以我会将所有重复的图像重命名为Item Holder,而不带数字。选择Item Holder (1),按住Shift,然后选择Item Holder (4),这样你就拥有了所有选中。现在,在Inspector中,在名称栏中输入Item Holder并按Enter 键。现在它们都应该重命名为Item Holder
  2. I don’t like my objects to have names with numbered parentheses in them, so I’ll rename all the duplicated Images Item Holder without the number. Select Item Holder (1), hold Shift, and select Item Holder (4) so that you have all of them selected. Now, in the Inspector, type Item Holder in the name slot and press Enter. They should all be renamed Item Holder now:
图 7.46:重命名元素的层次结构

图 7.46:重命名元素的层次结构

Figure 7.46: The Hierarchy of renamed elements

  1. 现在,让我们调整Bottom Right PanelHorizo​​ntal Layout Group组件的属性。为此,选择Bottom Right Panel,然后在其Horizo​​ntal Layout Group组件中为其赋予以下属性:
    图 7.47:右下面板的水平布局组组件

    图 7.47:右下面板的水平布局组组件

    您现在可以请参阅以下内容:

    图 7.48:生成的橙子面板

    图 7.48:生成的橙子面板

    通过调整Padding属性,我们将对象组移出父面板的边缘。我们通过更改Spacing属性将它们隔开,并通过启用控制子尺寸宽度高度来确保调整项目容器图像的尺寸以适合父面板

  2. Now, let’s adjust the properties on the Horizontal Layout Group component of the Bottom Right Panel. To do that, select the Bottom Right Panel, and in its Horizontal Layout Group component, give it the following properties:

    Figure 7.47: The Horizontal Layout Group component of the Bottom Right Panel

    You will now be able to see the following:

    Figure 7.48: The resulting Panel of oranges

    By adjusting the Padding properties, we brought the group of objects off the edge of the parent Panel. We spaced them apart by changing the Spacing property, and we ensured that the sizes of the Item Holder images were adjusted to fit onto the parent Panel by enabling Control Child Size Width and Height.

  1. 现在,剩下要做的就是将橙色图像换成其他四个物品。选择第二到第四个物品容器游戏对象的食物图像,并将其源图像更改为不同的食物。我使用了foodSpriteSheet_13foodSpriteSheet_22foodSpriteSheet_34foodSpriteSheet_45来获得以下结果:
  2. Now, all that’s left to do is swap out the orange images for four other items. Select the Food image of the second through fourth Item Holder GameObjects and change their Source Image to a different food. I used foodSpriteSheet_13, foodSpriteSheet_22, foodSpriteSheet_34, and foodSpriteSheet_45 to get the following results:
图 7.49:结果显示各种水果

图 7.49:结果显示各种水果

Figure 7.49: The resulting Panel of a variety of fruit

从这个例子中你可以看到,水平布局组组件和(类似的)垂直布局组组件设置起来并不太难,并且对于创建组织良好的zed 列表。

As you can see from this example, Horizontal Layout Group components and (similarly) Vertical Layout Group components aren’t too difficult to set up and are extremely useful for creating well-organized lists.

布置网格清单

Laying out a grid inventory

本章我们将介绍的最后一个例子是使用网格布局组组件和内容适配组件创建网格库存系统。我们将在后面的章节中继续研究此面板:

The last example we’ll cover in this chapter is the creation of a gridded inventory system using a Grid Layout Group component and the Content Fitter component. We’ll continue to work on this Panel in later chapters:

图 7.50:我们将在此示例中构建的电网清单

图 7.50:我们将在此示例中构建的电网清单

Figure 7.50: The grid inventory we will build in this example

要创建上图所示的网格库存系统,请完成以下步骤:

To create the gridded inventory system shown in the preceding screenshot, complete the following steps:

  1. 包含此库存系统的外壳与我们的暂停面板非常相似(见图7.49 )。由于它们非常相似,并且没有必要重新发明轮子,我们将只复制我们在第 6 章中创建的Pause Panel,并调整它的一些设置以获得正方形。在 Hierarchy 中选择Pause Panel ,然后按Ctrl + D复制它。现在,重命名复制的Inventory Panel。将其子项重命名为 Image Inventory Banner
  2. The shell that holds this inventory system looks remarkably similar to our Pause Panel (see Figure 7.49). Since they are so similar, and there is no reason to reinvent the wheel, we will just duplicate the Pause Panel we created in Chapter 6, and adjust some of its settings to get the square shape. Select Pause Panel in the Hierarchy and press Ctrl + D to duplicate it. Now, rename the duplicate Inventory Panel. Rename its child Image Inventory Banner:
图 7.51:复制和重命名后生成的层次结构

图 7.51:复制和重命名后生成的层次结构

Figure 7.51: The resulting Hierarchy after duplicating and renaming

笔记

Note

记得我们在第 6 章中向暂停面板添加了一个 Canvas Group 组件。通过复制它来创建库存面板库存面板也有一个Canvas Group组件。此组件将允许我们轻松隐藏和显示两个面板,我们将在下一章中执行此操作。

Remember that we added a Canvas Group component to the Pause Panel in Chapter 6. By duplicating it to create the Inventory Panel, the Inventory Panel has a Canvas Group component as well. This component will allow us to easily hide and show the two Panels, which we will do in the next chapter.

  1. 为了在示例截图中获得库存面板的方形外观,我们需要取消选择Image组件中的Preserve Aspect属性,然后调整Rect Transform组件属性。将WidthHeight值都更改500
    图 7.52:库存面板的 Rect Transform 和 Image 组件

    图 7.52:库存面板的 Rect Transform 和 Image 组件

    你现在应该在游戏视图中看到以下内容

    图 7.53:生成的库存面板

    图 7.53:生成的库存面板

  2. To get the square look of the Inventory Panel in the example screenshot, we need to deselect the Preserve Aspect property from the Image component and adjust the Rect Transform component properties. Change both the Width and Height values to 500:

    Figure 7.52: The Rect Transform and Image components of the Inventory Panel

    You should now see the following in your Game view:

    Figure 7.53: The resulting Inventory Panel

  1. 如果您查看7.50,您将看到库存物品组有一个精灵来勾勒出它的轮廓。这将充当网格的父对象。通过右键单击层次结构中的库存面板并选择UI |面板来创建此父对象。将此面板重命名为库存持有人
  2. If you look at Figure 7.50, you will see that the group of inventory items has an sprite that outlines it. This will act as the parent object of our grid. Create this parent object by right-clicking on the Inventory Panel in the Hierarchy and selecting UI | Panel. Rename this Panel Inventory Holder:
图 7.54:项目的层次结构

图 7.54:项目的层次结构

Figure 7.54: The Hierarchy of items

  1. 将源图像更改为uiElement_38并使用Color属性使图像完全不透明。
  2. Change the Source Image to uiElement_38 and use the Color property to make the Image fully opaque.
  3. 现在,Inventory Holder完全覆盖了Inventory Panel。但是,我们不需要更改任何Rect Transform组件的属性来调整其大小,因为我们将使用Content Size Fitter组件来调整大小。通过选择Add Component | Layout | Content Size Fitter ,将Content Size Fitter组件添加到Inventory Holder。不要更改此组件上的任何属性。由于Inventory Holder没有子项,因此现在调整Horizo​​ntal FitVertical Fit设置将导致它“消失”。
  4. Right now, the Inventory Holder is completely covering the Inventory Panel. However, we don’t need to change any of the Rect Transform component properties to resize it because we’ll use a Content Size Fitter component to adjust the size. Add a Content Size Fitter component to the Inventory Holder by selecting Add Component | Layout | Content Size Fitter. Don’t change any of the properties on this component yet. Since the Inventory Holder has no children, adjusting the Horizontal Fit and Vertical Fit settings now will cause it to “disappear.”
  5. 通过选择添加组件|布局|网格布局组,将网格布局组组件添加到库存支架。再次提醒,不要调整设置。我们将在添加子项后执行此操作。
  6. Add a Grid Layout Group component to the Inventory Holder by selecting Add Component | Layout | Grid Layout Group. Once again, don’t adjust the settings yet. We’ll do this once we add the children.
  7. 请注意,从7.50中可以看到,库存子项的设置与我们在上一个示例中创建的水平 HUD 中的子项一样。因此,我们将复制右下面板的子项,并移动重复项,使它们成为库存支架的子项。选择右下面板的第一个物品支架子项,按住Shift 键,然后选择右下面板的最后一个物品支架子项。这将选择所有子项。现在,选中所有子项,按Ctrl + D复制它们。
  8. Note from Figure 7.50 that the children of the inventory are set up just like the children in the horizontal HUD we created in the previous example. So, we’ll duplicate the children of the Bottom Right Panel and move the duplicates so that they are children of the Inventory Holder. Select the first Item Holder child of the Bottom Right Panel, hold down Shift, and select the last Item Holder child of the Bottom Right Panel. This will select all the children. Now, with all the children selected, press Ctrl + D to duplicate them.
  9. 单击层次结构中重复的Item Holder游戏对象并将其从右下方面板拖动到Inventory Holder ,使其成为Inventory Holder的子项
  10. Click and drag the duplicated Item Holder GameObjects in the Hierarchy from the Bottom Right Panel to the Inventory Holder, making them children of Inventory Holder:
图 7.55:项目的层次结构

图 7.55:项目的层次结构

Figure 7.55: The Hierarchy of items

  1. 选择其中一个Item Holder游戏对象并将其复制四次,这样一共有九个Item Holder子项。选择所有Item Holder子项并将它们重命名为Item Holder,这样它们的名称中就不再有数字了:
    图 7.56:项目的层次结构

    图 7.56:项目的层次结构

    现在你应该在游戏视图中看到类似于图 7.57内容。根据你由于复制或执行顺序不同,水果的顺序可能会略有不同。不过,这没关系!

    图 7.57:水果网格

    图 7.57:水果网格

  2. Select one of the Item Holder GameObjects and duplicate it four times so that there is a total of nine Item Holder children. Select all the Item Holder children and rename them Item Holder so that they no longer have a number in the name:

    Figure 7.56: The Hierarchy of items

    You should now see something similar to Figure 7.57 in your Game view. Depending on what you duplicated or the order in which you did it, the fruit may be in a slightly different order. That’s fine, though!

    Figure 7.57: The grid of fruit

  1. 现在,让我们调整库存支架面板上的网格布局组组件上的属性,以便子项将以 3x3 网格形式排列。调整属性以匹配以下屏幕截图中的属性:
  2. Now, let’s adjust the properties on the Grid Layout Group component on the Inventory Holder Panel so that the children will be laid out in a 3x3 grid. Adjust the properties to match those in the following screenshot:
图 7.58:库存持有者的网格布局组组件

图 7.58:库存持有者的网格布局组组件

Figure 7.58: The Grid Layout Group component of Inventory Holder

  1. 您现在应该看到在您的游戏视图中执行以下操作
    图 7.59:库存支架的网格布局组组件

    图 7.59:库存支架的网格布局组组件

    我们使用Spacing属性在每个单元格之间留出间距,使用Child Alignment Middle Center属性将子元素置于对象中心,并给出它是使用固定列数约束并将约束数 设置3 的3x3 布局

  2. You should now see the following in your Game view:

    Figure 7.59: The Grid Layout Group component of Inventory Holder

    We put spacing between each of the cells using the Spacing property, centered the children in the object using the Child Alignment Middle Center property, and gave it a 3x3 layout using Fixed Column Count Constraint and by setting Constraint Count to 3.

  1. 现在Inventory Holder有了子元素,我们可以更改其Content Size Fitter的设置。将Horizo​​ntal FitVertical Fit设置为Min Size
    图 7.60:Inventory Holder 的 Content Size Fitter 组件

    图 7.60:Inventory Holder 的 Content Size Fitter 组件

    你现在应该在游戏视图中看到以下内容

    图 7.61: 水果的拟合网格

    图 7.61: 水果的拟合网格

    虽然有点难以看清,但库存容器图像现在紧贴在物品容器图像网格周围。不过,我们需要一点填充。

  2. Now that Inventory Holder has children, we can change the settings of its Content Size Fitter. Set Horizontal Fit and Vertical Fit to Min Size:

    Figure 7.60: The Content Size Fitter component of Inventory Holder

    You should now see the following in your Game view:

    Figure 7.61: The fitted grid of fruit

    It’s a little hard to see, but the Image of Inventory Holder now fits snuggly around the grid of Item Holder Images. We want a bit of padding, though.

  1. 添加填充通过调整网格布局组中的填充属性,可以调整项目容器游戏对象(Item Holder GameObjects)的两侧,如下所示:
  2. Add padding to the sides of the Item Holder GameObjects by adjusting the Padding properties in the Grid Layout Group, as shown here:
图 7.62:水果的填充网格

图 7.62:水果的填充网格

Figure 7.62: The padded grid of fruit

  1. 现在所有东西都已排列整齐并正确定位,剩下要做的就是更改图像的顺序并更改最后四个插槽的图像。要更改图像的顺序,只需在层次结构中更改它们的顺序即可。网格布局组组件将自动为您重新定位场景中的项目。为了获得示例图像中的结果,我将最后四个食物项目的源图像更改为foodSpriteSheet_41foodSpriteSheet_52foodSpriteSheet_55foodSpriteSheet_53。这些更改导致以下结果已完成库存面板:
    图 7.63:各种食物的网格

    图 7.63:各种食物的网格

    就是这样。您现在应该有一个布局完美的库存网格。

    通过设置Grid Layout Group组件以及Content Size Fitter,我们现在可以更改网格中的项目数量,并且Inventory Holder将自动调整大小以适合所有项目,如下所示

    图 7.64:各种食物的小网格

    图 7.64:各种食物的小网格

    这确实有效好吧,直到我们尝试向库存中添加更多物品。你会发现,一旦我们有 10 个物品,一切看起来都很糟糕:

    图 7.65:各种食物的扩展网格

    图 7.65:各种食物的扩展网格

    我们可以采取一些措施来处理这个问题,包括更改单元格大小以及使用蒙版和Scroll Rect。我们将在后面的章节中讨论如何进行这些更改。不过现在,只需将库存保持在 9 件即可,这样一切看起来都很好。

    完成最后两章的所有示例后,您应该具有以下内容:

    图 7.66:第 6 章和第 7 章所有示例的结果

    图 7.66:第 6 章和第 7 章所有示例的结果

  2. Now that everything is lined up and positioned properly, the only thing left to do is change the order of the images and change the images of the last four slots. To change the order of the images, simply change their order in the Hierarchy. The Grid Layout Group component will automatically reposition the items within the scene for you. To get the result in the example image, I changed the Source Image of the last four Food items to foodSpriteSheet_41, foodSpriteSheet_52, foodSpriteSheet_55, and foodSpriteSheet_53. These changes result in the following completed Inventory Panel:

    Figure 7.63: The grid of various foods

    That’s it. You should now have a perfectly laid out inventory grid.

    With the Grid Layout Group component set up along with our Content Size Fitter, we can now change the number of items in the grid, and the Inventory Holder will automatically resize to fit all the items, as you can see here:

    Figure 7.64: The smaller grid of various foods

    This actually works really well, until we try to add more items to the inventory. You’ll see that once we have 10 items, everything looks pretty bad:

    Figure 7.65: The expanded grid of various foods

    There are a few things we can do to handle this, including changing the cell size and using a mask along with a Scroll Rect. We’ll discuss how to make those changes in a later chapter. For now, though, just leave your inventory at nine items so that everything looks nice.

    After completing all the examples in the last two chapters, you should have the following:

    Figure 7.66: The result of all examples from Chapters 6 and 7

这是为了添加自动布局到我们的场景。我们将在将来的章节中继续改进它之语。

And that’s in for adding automatic layouts to our scene. We’ll continue to improve upon it in future chapters.

概括

Summary

现在,我们知道了各种布局 UI 元素的技术。本章和上一章中介绍的信息提供了足够的工具来创建几乎任何你能想到的 UI 布局。

Now, we know all sorts of techniques to lay out our UI elements. The information covered in this chapter, and the last one, has provided enough tools to create almost any UI layout that you can imagine.

本章讨论的自动布局不仅仅在您想要手动添加 UI 项时有用,就像我们在本章中所做的那样。如果您想根据特定条件动态创建和添加 UI 项,这些自动布局尤其有用。

The automatic layouts discussed in this chapter aren’t just helpful when you want to manually add UI items, as we did in this chapter. These automatic layouts are particularly helpful if you want to dynamically create and add your UI items based on specific conditions.

在下一章中,我们将学习如何通过代码访问 UI 组件以及如何使用事件系统让玩家与UI 对象交互。

In the next chapter, we will learn how to access UI components via code and how to use the Event System to allow the player to interact with the UI objects.

8

8

事件系统和 UI 编程

The Event System and Programming for UI

Unity UI 系统的一个关键特性是能够轻松编程 UI 元素如何接收来自玩家的交互通过事件。事件系统是一个强大的系统,允许您创建和管理事件。

One of the key features of the Unity UI system is the ability to easily program how the UI elements receive interactions from the player via events. The Event System is a robust system that allows you to create and manage events.

一旦您学会如何利用事件系统,您将能够创建可交互的 UI 以及响应游戏中事件的 UI。

Once you learn how to take advantage of the Event System, you will be able to create interactable UI as well as UI that responds to events in your game.

在本章中,我们将讨论以下主题:

In this chapter, we will discuss the following topics:

  • 如何通过代码访问 UI 元素及其属性
  • How to access UI elements and their properties via code
  • 什么是事件系统以及如何使用
  • What the Event System is and how to work with it
  • 如何使用输入管理器自定义输入轴
  • How to customize input axes with the Input Manager
  • 什么是输入模块?Unity提供了哪些输入模块
  • What an Input Module is, and which ones are provided by Unity
  • 如何使用事件触发器组件接收UI 对象上的事件
  • How to use the Event Trigger component to receive events on UI objects
  • 什么是 Raycaster,以及Unity提供哪些类型的 Raycaster
  • What Raycasters are and what types of Raycasters are provided by Unity
  • 如何使用键盘输入显示和隐藏弹出面板
  • How to show and hide pop-up Panels using keyboard inputs
  • 如何暂停游戏
  • How to pause the game
  • 如何创建拖放式库存系统
  • How to create a drag and drop inventory system
  • 如何使用鼠标或多点触控输入来平移和缩放相机
  • How to use mouse or multi-touch input to pan and zoom the camera

技术要求

Technical requirements

您可以在此处找到本章节的相关代码和资产文件https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2008

You can find the relevant codes and asset files of this chapter here: https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2008

在代码中访问 UI 元素

Accessing UI elements in code

所有 UI 元素可以像其他 GameObject 一样在代码中访问和操作。要在代码中访问 UI 元素,必须包含UnityEngine.UI命名空间和正确的变量类型。让我们看看UnityEngine.UI命名空间。

All the UI elements can be accessed and manipulated in code like other GameObjects. To access a UI element in code, you must include the UnityEngine.UI namespace and the correct variable type. Let’s look at the UnityEngine.UI namespace.

UnityEngine.UI 命名空间

UnityEngine.UI namespace

命名空间​类的集合。当你在类中包含命名空间时,你表明你想要访问所有类中的变量和方法(函数)。使用 using关键字可在脚本顶部访问命名空间

A namespace is a collection of classes. When you include a namespace in your class, you are stating that you want to access all the variables and methods (functions) in your class. Namespaces are accessed at the top of a script with the using keyword.

默认情况下,所有新的 C# 脚本都包含System.CollectionsSystem.Collections.GenericUnityEngine命名空间。要通过代码访问 UI 元素的属性,必须首先使用UnityEngine.UI命名空间。

By default, all new C# scripts include the System.Collections, System.Collections.Generic and UnityEngine namespaces. To access the properties of UI elements via code, you must first use the UnityEngine.UI namespace.

因此,您需要在 C# 脚本的顶部包含以下行来表示您想要使用UnityEngine.UI命名空间:

Therefore, at the top of your C# script, you will need to include the following line to signify that you want to use the UnityEngine.UI namespace:

使用 UnityEngine.UI;
using UnityEngine.UI;

如果不使用命名空间,任何与 UI 元素相关的变量类型都会在代码编辑器中显示为红色,并且会出现编译器错误。一旦包含命名空间,变量类型就会变为蓝色文本,表示它是可用的变量类型,并且编译器错误会消失啊。

Without using the namespace, any variable type related to UI elements will be colored red in your code editor, and you will be given a compiler error. Once you include the namespace, the variable type will change to the blue-colored text, signifying that it is an available variable type, and the compiler error will disappear.

UI 变量类型

UI variable types

每种变量类型都是UnityEngine.UI命名空间内的类。因此,这些变量类型中的每一种都有自己的一组可以访问的变量和函数。我们将在以后的章节中更深入地讨论每种变量类型,但现在,我们只看一下在代码中访问 UI 元素属性的标准模板

Each variable type is a class within the UnityEngine.UI namespace. Therefore, each of these variable types, in turn, has its own set of variables and functions that can be accessed. We’ll discuss each variable type more thoroughly in future sections and chapters, but for now, let’s just look at the standard template for accessing a property of a UI element in code.

您可以在源文件中找到一个名为Chapter 08 .unitypackage 的Unity 包。导入它将引入一个名为Chapter8.unity的场景和各种代码文件。从包中导入项目并打开场景。在Chapter8场景中,您将看到一个名为UI Variables Example 的UI 图像。它没有分配精灵,显示为白色方块。以下脚本AddSprite.cs已附加UI 图像:

You can find within the source files a Unity package named Chapter 08.unitypackage. Importing it will bring in a scene named Chapter8.unity and various code files. Import the items from the package and open the scene. In the Chapter8 scene, you will see a UI Image named UI Variables Example. It does not have a sprite assigned to it and appears as a white square. The following script, AddSprite.cs, is attached to the UI Image:

使用System.Collections;
使用 System.Collections.Generic;
使用 UnityEngine;
使用 UnityEngine.UI;
公共类 AddSprite:MonoBehaviour {
     图像图像;
     公共 Sprite theSprite;
     无效唤醒(){
          theImage = GetComponent<Image>();
     }
     无效开始(){
          图像.精灵 = 精灵;
          图像.preserveAspect = true;
     }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class AddSprite : MonoBehaviour {
     Image theImage;
     public Sprite theSprite;
     void Awake(){
          theImage = GetComponent<Image>();
     }
     void Start () {
          theImage.sprite = theSprite;
          theImage.preserveAspect = true;
     }
}

上述代码中突出显示了特定于 UI 的代码片段。请注意,UnityEngine.UI命名空间包含在类的顶部。

The UI-specific pieces of code are highlighted in the preceding code. Note that the UnityEngine.UI namespace is included at the top of the class.

类中定义了两个公共变量:theImageImage类型,theSpriteSprite类型。theImage变量引用场景中的 UI Image,theSprite变量引用将成为UI Image源图像的精灵。

There are two public variables defined in the class: theImage, which is an Image type, and theSprite, which is a Sprite type. The theImage variable is referencing the UI Image in the scene and the theSprite variable is referencing the sprite that will become the source image of the UI Image.

Image变量类型位于UnityEngine.UI命名空间内,代表 UI Image GameObject。Sprite变量类型不是 UI 元素,包含在UnityEngine命名空间中。

The Image variable type is within the UnityEngine.UI namespace and represents UI Image GameObject. The Sprite variable type is not a UI element and is included in the UnityEngine namespace.

Start()函数中,通过在变量名称后键入句点然后键入属性来引用theImage上的Image组件的属性。您可以访问以此方式访问 UI 元素对应组件中出现的任何属性。您还可以通过这种方式访问​​组件中未列出的属性。

Within the Start() function, the properties of the Image component on theImage are referenced by typing a period and then the property after the variable name. You can access any property that appears in a UI element’s corresponding component in this way. You can also access properties that are not listed in the component this way.

附加到UI Variables Example (Image) 的AddSprite脚本出现在检查器中,如以下屏幕截图所示

The AddSprite script attached to UI Variables Example (Image) appears in the inspector, as shown in the following screenshot:

图 8.1:AddSprite 脚本及其属性

图 8.1:AddSprite 脚本及其属性

Figure 8.1: The AddSprite script and its properties

现在,播放场景时,精灵将从空白的白色方块变为香蕉图像,且其纵横比保持不变。

Now, when the scene is played, the sprite will change from a blank white square to an image of a banana with its aspect ratio preserved.

让我们探索事件系统,它将允许我们与我们进行交互r 用户界面。

Let’s explore the Event System, which will allow us to interact with our UI.

事件系统

The Event System

第 6 章中,我们了解到,当第一个 Canvas 添加到场景时,一个名为EventSystem的游戏对象会自动添加到层次结构中。事件系统允许您轻松接收玩家交互并通过事件将这些交互发送给场景中的对象。请注意,我说的是场景中的对象,而不是UI 对象。事件系统还允许您将事件发送给非 UI项目!

In Chapter 6, we learned that when the first Canvas is added to a scene, a GameObject named EventSystem is automatically added to the Hierarchy. The Event System allows you to easily receive player interactions and send those interactions to objects in your scene through events. Note that I said, objects in your scene and not UI objects. The Event System allows you to send events to non-UI items, too!

在我们继续之前,我想说明一下我对EventSystem(一个词)和 Event System(两个词)的使用,因为我将在这两个词之间来回切换。我想让你知道我是故意这样做的,并不是随便决定有时我讨厌空格键。

Before we proceed, I’d like to note my use of EventSystem (one word) and Event System (two words), because I will be switching back and forth between the two. I want you to know that I am doing it deliberately and am not just randomly deciding that sometimes I hate the spacebar.

我将使用EventSystem(一个词)来引用场景层次结构中出现的实际 GameObject,并使用 Event System(两个词)来引用处理事件的系统

I will use EventSystem (one word) to reference the actual GameObject that appears in the Hierarchy of your scene and Event System (two words) to reference the system that handles events.

事件系统除了向对象发送事件之外,它还为您做了很多事情。它还跟踪当前选定的游戏对象、输入模块和光线投射。

The Event System does quite a few things for you other than just sending events to objects. It also keeps track of the currently selected GameObject, the Input Modules, and Raycasting.

EventSystem GameObject 默认初始化三个组件:TransformEvent System Manager 和Standalone Input Module,如以下屏幕截图所示:

The EventSystem GameObject initializes, by default, with three components: the Transform, Event System Manager, and Standalone Input Module, as shown in the following screenshot:

图 8.2:EventSystem GameObject 及其组件

图 8.2:EventSystem GameObject 及其组件

Figure 8.2: The EventSystem GameObject and its components

由于EventSystem是一个 GameObject,因此它物理上存在于场景中(即使它没有可渲染的组件使其可见),因此像所有其他 GameObject 一样具有Transform组件。您现在应该熟悉Transform组件,因此我们不会进一步讨论它。但是,其他两个组件确实值得进一步讨论。现在让我们更仔细地看看事件系统组件。我们还将在本章输入模块部分讨论独立输入模块组件。

Since EventSystem is a GameObject, it physically exists within the scene (even though it has no renderable component making it visible) and therefore has a Transform component like all other GameObjects. You should be familiar with the Transform component by now, so we won’t discuss it further. However, the other two components do merit further discussion. Let’s look at the Event System component more closely now. We’ll also discuss the Standalone Input Module component in the Input Modules section of this chapter.

场景中不能有多个EventSystem GameObject。如果您尝试通过+ | UI | Event System在场景中添加新对象,则不会添加新对象,并且会为您选择场景中的当前对象。

You cannot have more than that one EventSystem GameObject in your scene. If you try to add a new one in the scene via + | UI | Event System, a new one will not be added, and the one currently in the scene will be selected for you.

笔记

Note

如果您设法在场景中添加第二个事件系统(例如使用Ctrl + D复制现有的事件系统),您将在控制台上看到一条警告消息

If you manage to add a second EventSystem to your scene (by perhaps using Ctrl + D to duplicate the existing one), you will see a warning message on your Console.

如果场景中有多个EventSystem GameObject,则只有第一个添加的 EventSystem 才会真正起作用。任何其他EventSystem都将不起作用。

If you have more than one EventSystem GameObject in your scene, only the first one added will actually do anything. Any additional EventSystems will be non-functional.

让我们看看事件系统 管理下一步。

Let’s look at the Event System Manager next.

事件系统经理

Event System Manager

事件系统经理是实际执行所有跟踪和管理各种事件系统元素的组件

Event System Manager is the component that actually does all the tracking and managing of the various Event System elements.

如果您想在不使用 UI 的情况下使用事件系统,则不会自动为您创建事件系统游戏对象。 您可以通过在对象的检查器上选择添加组件|事件|事件系统,将事件系统管理器添加到游戏对象。 让我们讨论一下事件系统管理器下的属性阿纳格。

If you want to work with the Event System without using UI, the EventSystem GameObject will not be automatically created for you. You can add an Event System Manager to a GameObject by selecting Add Component | Event | Event System on the object’s Inspector. Let’s talk about the properties under the Event System Manager.

首次入选

First Selected

你知道当你启动游戏时,Start Game按钮会为你突出显示,这样按Enter键就可以开始游戏,而不必使用鼠标?这就是First Selected属性为你做的。它会为你选择场景中的一个UI元素启动时自动运行

You know when you start up a game and the Start Game button is highlighted for you so that hitting Enter will start the game without you having to use your mouse? That’s what the First Selected property does for you. It selects a UI element in the scene for you automatically when it starts up.

您可以将任何难以处理的 UI 元素拖放到此插槽中,使其成为场景中第一个选定的 UI 项目。这对于不使用鼠标或触摸屏而仅依赖游戏手柄、操纵杆键盘。

You can drag and drop any intractable UI element into this slot to make it the first selected UI item in your scene. This is particularly helpful for games that do not use a mouse or touchscreen but rely solely on a gamepad, joystick, or keyboard.

发送导航事件

Send Navigation Events

可以打开或关闭“发送导航事件”属性。启用此属性后,您可以可以通过游戏手柄、操纵杆或键盘在 UI 元素之间导航。可以使用以下导航事件:

The Send Navigation Events property can be toggled on and off. When this property is enabled, you can navigate between UI elements via a gamepad, joystick, or keyboard. The following navigation events can be used:

  • 移动:您可以通过键盘上的箭头键或游戏手柄上的控制杆(或您指定为移动键的任何键/按钮)选择各种 UI 元素。移动将从指定为First Selected 的UI 项开始。我们将在第 10 章中讨论如何使用移动指定选择 UI 项的顺序
  • Move: You can select the various UI elements via arrow keys on the keyboard or the control stick on a gamepad (or whichever keys/buttons you have designated as the movement keys). Movement will start at the UI item designated First Selected. We will discuss how to specify the order in which UI items are selected using movement in Chapter 10.
  • 提交:提交所选的 UI 项目。
  • Submit: Commit to the UI item selected.
  • 取消取消选择。
  • Cancel: Cancel the selection.

阻力阈值

Drag Threshold

Drag Threshold属性表示 UI 对象可以移动的像素数才会被视为被拖拽。人们的手不是完全稳定的,所以当他们试图点击或轻触 UI 项目时,他们的鼠标或手指可能会稍微移动。这个拖拽阈值允许玩家在他们选择的项目被拖拽之前稍微移动他们的输入(或者如果你把这个数字设得很高,可以移动很多),而不是点击了

The Drag Threshold property represents the number of pixels a UI object can be moved before it is considered being dragged. People don’t have perfectly steady hands, so when they are trying to click or tap a UI item, their mouse or finger may move slightly. This Drag Threshold allows the player to move their input slightly (or a lot if you make this number high) before the item they are selecting is dragged rather than clicked.

输入管理器

Input Manager

我们讨论事件系统管理器的下一个组件,我想讨论输入管理器。输入管理器是您在游戏中定义轴的地方,方法是将它们分配给鼠标、键盘或操纵杆(游戏手柄)上的按钮。这还允许您在编码时使用轴名称来轻松引用要在操作中执行的所有输入。

Before we discuss the next component of the Event System Manager, I want to discuss the Input Manager. The Input Manager is where you define the axes in your game by assigning them to the buttons on your mouse, keyboard, or joystick (gamepad). This also allows you to use the axis name when coding to easily reference all inputs that you want to perform in an action.

笔记

Note

请记住,正如我们在第 5 章中讨论的那样,实际上有两个系统可让您处理游戏中的输入:输入管理器和新输入系统。本章将重点介绍输入管理器。我们将在以后的章节中讨论新输入系统

Remember, as we discussed in Chapter 5, there are actually two systems that will allow you to handle input in your game: the Input Manager and the new Input System. This chapter will focus on the Input Manager. We will discuss the new Input System in a future chapter.

要打开输入管理器,请选择编辑|项目设置|输入管理器

To open the Input Manager, select Edit | Project Settings | Input Manager.

如果你选择Axes旁边的箭头,您将看到默认的轴列表

If you select the arrow next to Axes, you will see the default list of axes:

图 8.3:输入管理器及其所有预定义轴

图 8.3:输入管理器及其所有预定义轴

Figure 8.3: The Input Manager and all its pre-defined axes

默认情况下,总共有 30 个轴。更改“大小”旁边的数字将为您提供更多或更少的轴。展开单个轴将显示以下内容:

There are 30 total axes by default. Changing the number next to Size will give you more or less axes. Expanding the individual axes will reveal the following:

图 8.4:第一个水平输入轴

图 8.4:第一个水平输入轴

Figure 8.4: The first Horizontal input axis

这个词名称栏中输入的内容将显示在可扩展箭头旁边。在上图,已定义允许水平移动的所有键

The word entered in the Name slot is what will appear next to the expandable arrow. In the preceding screenshot, all the keys that allow for horizontal movement have been defined.

请注意,键盘的左右箭头以及AD键默认为水平移动。

Note that the left and right arrows, along with the A and D keys of a keyboard, are defaulted to the Horizontal movement.

列表下方还有第二个水平轴。它配置为与操纵杆或游戏手柄配合使用。

There is also a second Horizontal axis further down the list. It is configured to work with a joystick or a gamepad.

图 8.5:第二个水平输入轴

图 8.5:第二个水平输入轴

Figure 8.5: The second Horizontal input axis

就像那里是两个标记为“水平”的轴,它们都可以在代码中使用水平”标签轻松引用

As there are two Axes labeled Horizontal, they can both be easily referenced in code with the "Horizontal" label.

笔记

Note

查看列表每个键盘键的关键字以及每个轴输入属性的描述,请访问https://docs.unity3d.com/Manual/class-InputManager.xhtml

To view a list of the keywords for each keyboard key as well as a description for each of the properties of an axis input, visit https://docs.unity3d.com/Manual/class-InputManager.xhtml.

这样您就可以将所有这些按钮和操纵杆作为一个组引用。这比编写代码让每个单独的键盘键与单独的操纵杆相关联要简单得多。

This will allow you to reference all these buttons and joysticks together as a group. This is much simpler than having to write code that gets each of the individual keyboard keys along the individual joysticks.

您可以通过右键单击并选择“删除数组元素”来删除这 30 个默认轴中的任何一个。

You can delete any of these 30 default axes you want by right-clicking on them and selecting Delete Array Element.

但是,删除它们时要小心。您至少需要一个提交轴和一个取消轴才能使用独立输入管理器(除非您更改独立输入管理器中的提交按钮取消按钮)。有关更多信息,请参阅本章的独立输入管理器部分。

However, be careful when you delete them. You need at least one Submit axis and one Cancel axis to be able to use the Standalone Input Manager (unless you change the Submit Button and Cancel Button in the Standalone Input Manager). For more information, refer to the Standalone Input Manager section of this chapter.

现在我们已经探索了输入管理器,我们可以查看按钮的各种输入功能以及d键按下。

Now that we have explored the Input Manager, we can review the various input functions for buttons and key presses.

按钮和按键的输入功能

Input functions for buttons and key presses

那里通过代码访问按键和按钮按下的方法有很多。具体方法取决于您是否在输入管理器中将按键指定为轴,以及您是否希望按键注册一次或连续注册。我将在本文中讨论其中几种方法,但您可以https://docs.unity3d.com/ScriptReference/Input.xhtml找到完整的函数列表

There are quite a few ways to access key and button presses via code. How you do this depends on whether you have the key specified as an axis in the Input Manager and whether you want the key to register once or continuously. I’ll discuss a few in this text, but you can find a full list of the functions at https://docs.unity3d.com/ScriptReference/Input.xhtml.

在本章前面我们回顾的第 8 章示例场景中,一个名为KeyPresses.cs的脚本附加到主摄像头。如果您想尝试一下,KeyPresses.cs脚本包含本节中演示的所有代码按下第键。

A script named KeyPresses.cs is attached to the Main Camera in the Chapter8 example scene we were reviewing earlier in this chapter. The KeyPresses.cs script contains all the code demonstrated in this section if you’d like to play around with key presses.

获取按钮

GetButton

如果你在输入管理器中有一个按钮定义为轴,你可以使用GetButton()GetButtonDown()GetButtonUp()来确定按钮何时被按下。

If you have a button defined as an axis in the Input Manager, you can use GetButton(), GetButtonDown(), and GetButtonUp() to determine when a button has been pressed.

按住按钮时,GetButton()返回true;在按钮最初被按下的帧上,GetButtonDown()仅返回true一次;在按钮被释放的帧上,GetButtonUp()仅返回true一次。

GetButton() returns true while the button is being held, GetButtonDown() returns true only once, on the frame that the button is initially pressed, and GetButtonUp() returns true only once, on the frame that the button is released.

在每个函数中,将输入管理器中的轴名称放在引号内的括号内。通常,这些函数应在脚本的Update()函数中调用,以便可以随时触发它们

Within each of the functions, you place the axis name from the Input Manager within the parentheses within quotes. Generally, these functions should be called within the Update() function of a script so that they can be triggered at any time.

因此,例如,如果您想检查是否按下了Enter键,由于它被分配给了Submit轴的Positive Button,您可以编写以下代码来在按下Enter键时触发:

So, for example, if you wanted to check whether the Enter key is being pressed, since it is assigned to a Positive Button for the Submit axis, you can write the following code to trigger when the Enter key is pressed down:

无效更新(){
     如果(Input.GetButtonDown(“提交”)){
          Debug.Log("您按下了提交键/按钮!");
     }
}
void Update () {
     if (Input.GetButtonDown("Submit")){
          Debug.Log("You pressed a submit key/button!");
     }
}

记住这不会仅通过Enter键触发,因为提交轴有几个键分配给Positive ButtonAlt Positive Button

Keep in mind that this will not just trigger with the Enter key, as the Submit axis has a few keys assigned to the Positive Button and Alt Positive Button.

笔记

Note

需要注意的是,如果你玩第 8 章场景,并想观看这些按钮和按键触发控制台日志消息,你必须首先在游戏视图中单击,以便输入重新游戏中的大师。

It’s important to note that if you play the Chapter8 scene and want to watch these button and key presses fire the console log messages, you must first click within the Game View so that the inputs will register in the game.

获取轴

GetAxis

如果你寻找一个可以连续触发的功能如果在发射之间没有任何停顿,则需要使用GetAxis()而不是GetButton()。GetButton ()适用于您想要按住但希望在事件发射之间稍作停顿的按钮(想象一下按住射击按钮,枪会在发射子弹之间停顿)。由于这种连续的帧速率独立执行,GetAxis()更适合涉及移动的事件。

If you’re looking for a function that will trigger continuously without any breaks between firing, you want to use GetAxis() rather than GetButton(). GetButton() is good for buttons you want to hold down but want a slight pause between events firing (think of holding down a fire button, and the gun shoots bullets with breaks in between them). GetAxis() works better for events involving movement because of this continuous frame-rate independent execution.

GetAxis() 的工作方式略有不同,因为它返回的是浮点值,而不是布尔值,例如GetButton()。它也最适合在Update()函数中使用。因此,例如,您可以按如下方式检查水平移动是否正在发生:

GetAxis() works a bit differently, as it returns a float value rather than a bool, such as GetButton(). It is also best suited within an Update() function. So, for example, you can check whether the horizontal movement is occurring as follows:

无效更新(){
     float Horizo​​ntalValue = Input.GetAxis("水平");
     如果 (horizo​​ntalValue != 0){
          Debug.Log("您正在按住水平按钮!");
     }
}
void Update () {
     float horizontalValue = Input.GetAxis("Horizontal");
     if (horizontalValue != 0){
          Debug.Log("You're holding down a horizontal button!");
     }
}

获取密钥

GetKey

如果你想获取未分配的键盘按键对于轴,您可以使用GetKey()GetKeyDown()GetKeyUp()通过 KeyCode引用键盘键

If you want to get a keyboard key press that is not assigned to an axis, you can use GetKey(), GetKeyDown(), or GetKeyUp() to reference keyboard keys via their KeyCode.

GetKey ()函数的工作原理与GetButton()函数非常相似。当按键被按下时,GetKey()返回true ; GetKeyDown()仅在按键最初被按下的帧上返回true一次;而GetKeyUp()仅在按键被释放的帧上返回true一次。

The GetKey() functions work pretty similar to the GetButton() functions. GetKey() returns true while the key is being held down; GetKeyDown() returns true only once, on the frame that the key is initially pressed; and GetKeyUp() returns true only once, on the frame that the key is released.

每个键都有自己的KeyCode ,需要在GetKey()函数的括号中引用。您可以https://docs.unity3d.com/ScriptReference/KeyCode.xhtml找到所有键盘KeyCode值的列表

Each key has its own KeyCode that needs to be referenced in the parentheses of the GetKey() functions. You can find a list of all the keyboard KeyCode values at https://docs.unity3d.com/ScriptReference/KeyCode.xhtml.

因此,例如,如果您想检查是否按下了字母数字键盘上的8键,您可以编写以下代码来在按下8键时触发:

So, for example, if you wanted to check whether the 8 key from the alphanumeric keyboard is being pressed, you could write the following code to trigger when the 8 key is pressed down:

无效更新(){
     如果(输入.GetKeyDown(KeyCode.Alpha8)){
          Debug.Log("您由于某种原因按下了8键!");
     }
}
void Update () {
     if (Input.GetKeyDown(KeyCode.Alpha8)){
          Debug.Log("You pressed the 8 key for some reason!");
     }
}

获取鼠标按钮

GetMouseButton

正如使用GetButton()GetKey(),有三个函数用于检查鼠标按钮是否被按下:GetMouseButton()GetMouseButtonDown()GetMouseButtonUp()。它们返回true 的方式与GetButton()GetKey() 函数相同。

Just as with GetButton() and GetKey(), there are three functions for checking when a mouse button has been pressed: GetMouseButton(), GetMouseButtonDown(), and GetMouseButtonUp(). They return true in the same way that the GetButton() and GetKey() functions do.

您也可以将这些函数放在Update()函数中。在括号内,您可以检查按下的是哪个按钮;0表示左键单击,1表示右键单击,2表示中键单击。

You’d place these functions within the Update() function as well. Within the parentheses, you check to see which button is being pressed; 0 represents a left-click, 1 represents a right-click, and 2 represents a middle-click.

例如,如果您想要检查鼠标中键是否被单击,您可以编写以下代码来在鼠标中键被按下时触发:

So, for example, if you wanted to check that the middle mouse button was clicked, you could write the following code to trigger when the middle mouse button is pressed down:

无效更新(){
     如果(输入.GetMouseButtonDown(2)){
          Debug.Log("您按下了鼠标中键!");
     }
}
void Update () {
     if (Input.GetMouseButtonDown(2)){
          Debug.Log("You pressed the middle mouse button!");
     }
}

现在我们已经回顾了按钮和按键的输入功能,让我们回顾一下输入模块。

Now that we’ve reviewed the input function for buttons and key presses, let’s review the Input Modules.

输入模块

Input Modules

输入模块描述事件系统如何通过鼠标、键盘、触摸屏、游戏手柄等处理游戏输入。你可以将它们视为硬件和事件之间的桥梁。

Input Modules describe how the Event System will handle the inputs to the game via the mouse, keyboard, touchscreen, gamepad, and so on. You can think of them as the bridge between the hardware and events.

Unity提供了三种输入模块:

There are three input modules provided by Unity:

  • 独立输入模块
  • Standalone Input Module
  • 基本输入模块
  • Base Input Module
  • 指针输入模块
  • Pointer Input Module

要使用这些输入模块,您需要将它们作为组件附加到EventSystem GameObject。

To utilize these input modules, you attach them as components to your EventSystem GameObject.

您不限于使用这三个输入模块,也可以创建自己的输入模块,因此如果您有一个未被这三个模块之一覆盖的输入设备,您可以创建自己的输入模块脚本,然后将其附加到事件系统。

You are not restricted to using these three input modules and can create your own, so if you have an input device that is not covered by one of those three, you’d create your own input module script and then attach it to the Event System.

还有另一个输入模块,称为触摸输入模块,它曾经是触摸屏输入所必需的。然而,这个模块已被弃用,其功能现在被集中起来进入独立输入模块。由于此输入模块已被弃用,因此本文不再讨论。

There is another input module called Touch Input Module, which used to be necessary for touchscreen inputs. However, this module has been deprecated and its functionality is now lumped into the Standalone Input Module. Since this input module has been deprecated, it will not be discussed in this text.

我们来看看三个输入模块Unity 提供的深入介绍。

Let’s look at the three input modules provided by Unity in depth.

独立输入模块

Standalone Input Module

独立输入模块是一个非常强大的输入模块,可以与大多数你的输入设备。它可与鼠标、键盘、触摸屏和游戏手柄配合使用。

The Standalone Input Module is a pretty robust input module that will work with most of your input devices. It works with a mouse, keyboard, touchscreen, and gamepad.

创建事件系统游戏对象时,独立输入模块会自动添加到其中。但是,您可以使用对象检查器上的添加组件|事件|独立输入模块将独立输入模块作为组件附加。如果您想添加第二个模块、之前删除了它并想重新附加它,或者想将独立输入模块添加到另一个游戏对象,您可以执行此操作。

The Standalone Input Module is automatically added to your EventSystem GameObject when it is created. However, you can attach the Standalone Input Module as a component using Add Component | Event | Standalone Input Module on the object’s Inspector. You could do this if you wanted to add a second one, previously deleted it, and want to re-attach it, or want to add the Standalone Input Module to another GameObject.

图 8.6:独立输入模块组件

图 8.6:独立输入模块组件

Figure 8.6: The Standalone Input Module component

您将看到独立输入模块的前四个属性是水平轴垂直轴提交按钮取消按钮。这些属性是我在讨论输入模块之前想讨论输入管理器的原因。分配给这些插槽的默认属性是水平垂直提交取消。这些分配引用了输入管理器的轴分配

You’ll see that the first four properties of the Standalone Input Module are Horizontal Axis, Vertical Axis, Submit Button, and Cancel Button. These properties are the reason I wanted to discuss the Input Manager before discussing the Input Modules. The default properties assigned to these slots are Horizontal, Vertical, Submit, and Cancel. These assignments are referencing the axes assignments from the Input Manager.

每秒输入操作数属性定义每秒允许的输入次数。这与键盘和游戏手柄输入有关。默认值为10。这方法在输入操作之后,下一个输入操作被注册之前会有十分之一秒的延迟。重复延迟属性是每秒输入操作发生之前的时间量(以秒为单位)

The Input Actions Per Second property defines how many inputs are allowed per second. This is in relation to the keyboard and the gamepad inputs. The default value is 10. This means that there will be a tenth of a second delay after an input action before the next input action is registered. The Repeat Delay property is the amount of time, in seconds, before Input Actions Per Second occurs.

将强制模块活动属性设置为 true 将使该独立输入模块处于活动状态。

Setting the Force Module Active property to true will make this Standalone Input Module active.

笔记

Note

您可以在以下位置了解有关独立输入模块的更多信息

You can learn more about the Standalone Input Module at the following locations:

https://docs.unity3d.com/Packages/com.unity.ugui@1.0/manual/script-StandaloneInputModule.xhtml

https://docs.unity3d.com/Packages/com.unity.ugui@1.0/manual/script-StandaloneInputModule.xhtml

https://docs.unity3d.com/2019.1/Documentation/ScriptReference/EventSy词干.StandaloneInputModule.xhtml

https://docs.unity3d.com/2019.1/Documentation/ScriptReference/EventSystems.StandaloneInputModule.xhtml

基本输入模块/指针输入模块

BaseInputModule/PointerInputModule

BaseInputModule和PointerInputModule是只能通过代码访问的模块

The BaseInputModule and PointerInputModule are modules that are only accessible via code.

如果你需要创建您的自己的输入模块,您将通过以下方式创建它BaseInputModule扩展。您可以https://docs.unity3d.com/Packages/com.unity.ugui@1.0/api/UnityEngine.EventSystems.BaseInputModule.xhtml查看通过扩展BaseInputModule 可以利用的变量、函数和消息的完整列表

If you need to create your own Input Module, you will create it by extending from the BaseInputModule. You can view a full list of the variables, functions, and messages that can be utilized by extending the BaseInputModule at https://docs.unity3d.com/Packages/com.unity.ugui@1.0/api/UnityEngine.EventSystems.BaseInputModule.xhtml.

PointerInputModule是前面描述的独立输入模块使用的BaseInputModule。它还可用于编写自定义输入模块。您可以https://docs.unity3d.com/Packages/com.unity.ugui@1.0/api/UnityEngine.EventSystems.PointerInputModule.xhtml上查看可通过扩展PointerInputModule使用的变量、函数和消息的完整列表

The PointerInputModule is a BaseInputModule that is used by the Standalone Input Module described earlier. It can also be used to write custom Input Modules. You can view a full list of the variables, functions, and messages that can be utilized by extending the PointerInputModule at https://docs.unity3d.com/Packages/com.unity.ugui@1.0/api/UnityEngine.EventSystems.PointerInputModule.xhtml.

现在,让我们看看如何访问多点触控输入适用于移动设备和触摸屏设备。

Now, let’s look at how we can access multi-touch input on mobile and touchscreen devices.

多点触控输入

Input for multi-touch

访问多点触摸非常简单。您可以使用Input.GetTouch(index)访问触摸,其中索引表示触摸的索引,第一个触摸发生在索引0处。从那里,您可以以几乎相同的方式访问信息就像访问鼠标信息一样。您还可以使用Input.touchCount了解总共发生了多少次触摸。有关如何访问多点触摸输入的示例,请参阅本章的示例部分。

Accessing multi-touch is pretty easy. You access touches with Input.GetTouch(index), where the index represents the index of the touch, with the first touch occurring at index 0. From there, you can access information pretty much in the same way as accessing information about a mouse. You can also find out how many total touches are occurring with Input.touchCount. See the Examples section of this chapter for an example of how to access multi-touch input.

移动设备还配有加速度计和陀螺仪,为设备提供输入。让我们看看如何访问这些输入。

Mobile devices also have accelerometers and gyroscopes providing input to the device. Let’s look at how you can access those inputs.

加速度计和陀螺仪输入

Input for accelerometer and gyroscope

您可以访问使用Vector3 Input.acceleration属性从设备的加速度计获取数据。Input.acceleration的坐标根据设备的旋转与场景对齐,如下所示:

You can access data from the device’s accelerometer using the Vector3 Input.acceleration property. The coordinates of Input.acceleration line up with the scene based on the rotation of the device, as shown:

图 8.7:基于屏幕旋转的世界轴

图 8.7:基于屏幕旋转的世界轴

Figure 8.7: The world axes based on screen rotation

简单示例其中涉及在移动设备时在场景中移动物体,在对象的Update()函数中使用类似下面的内容:

Simple examples of this involve moving an object around a scene when the device is moved, using something like the following within an Update() function on the object:

变换.平移(输入.加速度.x, 0,-输入.加速度.y);
transform.Translate(Input.acceleration.x, 0, -Input.acceleration.y);

陀螺仪使用更复杂的数学方法,通过Gyroscope类获得更精确的屏幕运动。请记住,许多设备都不支持陀螺仪,因此最好尽可能使用加速度计。

The gyroscope uses more complicated mathematics to get a more precise movement of the screen using the Gyroscope class. Remember, the gyroscope is not supported on many devices, so it’s best to use the accelerometer when possible.

笔记

Note

有关如何在 iOS 设备上使用陀螺仪的示例,请参见此处:https://docs.unity3d.com/ScriptReference/Gyroscope.xhtml

An example of how to use the gyroscope on an iOS device can be found here: https://docs.unity3d.com/ScriptReference/Gyroscope.xhtml.

现在我们已经回顾了各种输入模块,让我们回顾一下事件触发器。

Now that we’ve reviewed the various input modules, let’s review Event Triggers.

事件触发器

Event Trigger

事件触发器组件可以附加到任何 UI(或非 UI)元素,以允许对象接收事件。一些 UI 元素已预先配置为拦截特定事件。例如,按钮具有onClick事件。但是,如果您想向尚未设置为接收事件的对象添加事件,或者您希望它接收不同的事件,您可以将事件触发器组件附加到GameObject。

The Event Trigger component can be attached to any UI (or non-UI) element to allow the object to receive events. Some of the UI elements are preconfigured to intercept specific events. For example, buttons have the onClick event. However, if you’d like to add an event to an object that either isn’t already set up to receive events or you want it to receive different events, you can attach an Event Trigger component to the GameObject.

您可以通过选择添加组件|事件|事件触发器来附加事件触发器组件

You can attach an Event Trigger component by selecting Add Component | Event | Event Trigger.

使用事件触发器组件的一个注意事项是,它所附加的对象会接收所有事件,而不仅仅是您添加的事件。因此,即使您没有告诉对象如何处理指定的事件,它也会接收该事件并确认事件已发生 - 它只是不会做出任何响应。这可能会降低游戏的性能。如果您担心性能,您需要编写自己的脚本,仅将要使用的事件附加到您的组件。下一节“事件输入”将讨论如何实现这一点。

One caveat of using the Event Trigger component is that the object it is attached to receives all the events, not just the ones you added. So, even if you don’t tell the object what to do with the specified event, it will receive that event and acknowledge that the event occurred—it just won’t do anything in response. This can slow the performance of your game. If you are worried about performance, you will want to write your own script that attaches only the events you want to use to your component. The next section, Event Inputs, discusses how to achieve this.

如果在 UI 元素以外的对象上使用事件触发器组件,则该对象还必须具有对撞机组件,并且必须在场景内的相机上包含射线投射器。

If you use an Event Trigger component on an object other than a UI element, the object must also have a collider component, and you must include a raycaster on the camera within the scene.

使用哪种对撞机和射线投射器取决于您是在 2D还是 3D 中工作。

Which collider and raycaster you use depends on whether you are working in 2D or 3D.

如果您在 2D 中工作,则可以使用添加组件|物理 2D向对象添加 2D 碰撞器,然后从对象的检查器中选择适当的 2D 碰撞器。然后,您可以通过在相机的检查器中选择添加组件|事件|物理 2D 射线投射器向相机添加射线投射器

If you are working in 2D, you can add a 2D collider to the object with Add Component | Physics 2D and then select the appropriate 2D collider from within the object’s Inspector. You can then add a raycaster to the camera by selecting Add Component | Event | Physics 2D Raycaster from within the camera’s Inspector.

如果您在 3D 环境中工作,则可以使用“添加组件” | “物理”将 3D 碰撞器添加到对象,然后从对象的检查器中选择适当的 3D 碰撞器。然后,您可以通过在相机的检查器中选择“添加组件” | “事件” | “物理射线投射器”将射线投射器添加到相机

If you are working in 3D, you can add a 3D collider to the object with Add Component | Physics and then select the appropriate 3D collider from within the object’s Inspector. You can then add a raycaster to the camera by selecting Add Component | Event | Physics Raycaster from within the camera’s Inspector.

让我们看看各种事件事件触发器可以接收的类型。

Let’s look at the various event types that the Event Trigger can receive.

事件类型

Event types

你可以告诉通过选择“添加新事件类型”来选择您想要接收哪种类型的输入事件的对象

You can tell the object which type of input event you want to receive by selecting Add New Event Type.

许多这些事件与对象的边界区域相关。UI 对象的边界区域由 Rect Transform 的区域表示。对于非 UI 对象,边界区域由 Rect Transform 的区域表示。离子由二维或三维对撞机表示

Many of these events are tied to the bounding region of the object. The bounding region of a UI object is represented by the area of the Rect Transform. For a non-UI object, the bounding region is represented by a 2D or 3D collider.

指针事件

Pointer events

指针事件可以通过独立输入模块中的指针调用。请记住,指针并非仅仅是鼠标。独立输入模块中的指针可以是鼠标、手指触摸或与游戏手柄移动绑定的十字线。

Pointer events can be called by the pointer in a Standalone Input Module. Remember that a pointer is not exclusively a mouse. The pointer in a Standalone Input Module can be a mouse, finger touch, or a reticle tied to gamepad movement.

其中两种事件类型与指针相对于对象边界框区域的位置有关。当指针进入对象的边界框时,将调用PointerEnter事件;当指针离开边界框区域时,将调用PointerExit事件

Two of the event types are related to the position of the pointer in relation to the object’s bounding box region. The PointerEnter event is called when the pointer enters the bounding box of the object and PointerExit is called when the pointer exits the bound box area.

有三个事件与单击对象有关。当在对象的边界区域内按下指针时,将调用PointerDown事件;当在对象的边界区域内释放指针时,将调用PointerUp 事件。需要注意的是,使用PointerUp时,可以在对象外部按下指针,按住,然后在对象内部释放,以触发PointerUp事件。当按下指针然后释放时,将调用PointerClick事件d 在对象的边界区域内。

There are three events related to clicking on the object. The PointerDown event is called when the pointer is pressed down within the bounding region of the object, and PointerUp is called when the pointer is released within the bounding region of the object. It’s important to note that with PointerUp, the pointer can be pressed outside of the object, held down, and then released inside the object for the PointerUp event to trigger. The PointerClick event is called when the pointer is pressed and then released within the bounding region of the object.

拖放事件

Drag and Drop events

工作时对于各种拖放事件,区分被拖动的对象和拖动对象所放置的对象非常重要。

When working with the various drag and drop events, it’s important to differentiate between the object being dragged and the object on which the dragged object is dropped.

无论何时发现拖动对象,但在实际拖动对象之前,都会调用InitializePotentialDrag事件。

The InitializePotentialDrag event is called whenever a drag object is found, but before an object is actually being dragged.

当拖动对象时,会在该对象上调用Drag事件。当在对象的边界框内按下指针,然后移动而不释放时,会发生Drag事件。释放指针即可结束该事件。当拖动对象开始拖动时,会从该对象调用BeginDrag事件;当拖动结束时,会调用EndDrag事件

The Drag event is called on the object being dragged when it is being dragged. A Drag event occurs when a pointer is pressed within the bounding box of an object and then moved without releasing. It’s ended by releasing the pointer. The BeginDrag event is called from the object being dragged when its drag begins, and the EndDrag event is called when its drag ends.

Drop事件​与EndDrag事件不同。EndDrag事件在刚刚被拖动的对象上调用。Drop事件由拖动对象被放置到的对象调用。因此,当拖动对象停止拖动时, Drop事件由触摸拖动对象的对象调用。因此,如果您正在制作拖放菜单,则需要将Drag事件添加到要拖动的对象,并将Drop 事件将被放入他们将被放入的插槽中

The Drop event is different from the EndDrag event. The EndDrag event is called on the object that was just being dragged. The Drop event is called by the object on which the dragged object was dropped. Therefore, the Drop event is called by the object touching the dragged object when the dragged object stops dragging. So, if you were making a drag and drop menu, you’d add the Drag event to the objects you want to drag and the Drop event to the slots they will be dropped into.

甄选活动

Selection events

选择事件是当对象被视为选定对象时调用,当对象不再被视为选定时调用Deselect 。每个事件都只触发一次 - 当对象被视为选定或取消选定时。如果您想要一个在对象被选定时持续触发的事件,您可以使用UpdateSelected事件。每帧都会调用UpdateSelected事件

The Select event is called when the object is considered the selected object and Deselect is called when the object is no longer considered selected. Each of these events only fires once—the moment the object is considered selected or deselected. If you want an event that will trigger continuously while the object is selected, you can use the UpdateSelected event. The UpdateSelected event is called every frame.

其他活动

Other events

其他活动根据输入管理器中的分配进行调用。请记住,您可以将按钮、键等分配给定义移动、提交和取消的轴。让我们讨论一下其中的一些事件。

Other events are called based on assignments in the Input Manager. Remember that you can assign buttons, keys, and such to axes that define movement, submit, and cancel. Let’s talk about a few of these events.

当鼠标滚轮滚动时,将调用 Scroll 事件;当发生移动时,将调用Move事件。当按下分配给Submit轴的按钮时,将调用Submit事件;当按下分配给Cancel轴被按下时,取消事件被调用。

The Scroll event is called when the mouse wheel scrolls and the Move event is called when a movement happens. When the button assigned to the Submit axis is pressed, the Submit event is called and when the button assigned to the Cancel axis is pressed, the Cancel event is called.

向事件添加动作

Adding an action to the event

一旦你真正选择事件类型后,您必须指定触发该事件类型时将发生什么。以下屏幕截图显示了选择Pointer Enter作为事件类型的结果

Once you have actually selected an event type, you must specify what will happen when that event type triggers. The following screenshot shows the results of selecting Pointer Enter as an event type:

图 8.8:事件触发器组件

图 8.8:事件触发器组件

Figure 8.8: The Event Trigger component

上图显示已选择Pointer Enter事件类型,但尚未定义当指针进入对象边界区域时会发生什么。要定义触发事件时会发生什么,您必须选择事件框右下角的+号。您可以通过多次选择+号来添加触发事件时的多个操作。

The preceding screenshot shows that an event type of Pointer Enter has been selected, but what happens when the pointer enters the object’s bounding area is yet to be defined. To define what happens when the event triggers, you must select the + sign at the bottom-right corner of the event’s box. You can add multiple actions when the event triggers by selecting the + sign multiple times.

一旦事件类型被添加到事件触发器组件,它就不能再被添加,并且会在添加新事件类型列表中显示为灰色。

Once an event type has been added to the Event Trigger component, it cannot be added a second time and will be grayed out in the Add New Event Type list.

要从事件触发器组件中删除事件类型,请选择事件类型框右上角的-符号。

To remove an event type from the Event Trigger component, select the sign at the top-right corner of the event type’s box.

一旦选择加号,事件类型应该如下所示:

Once the plus sign is selected, the event type should look as follows:

图 8.9:带有指针进入事件的事件触发器组件

图 8.9:带有指针进入事件的事件触发器组件

Figure 8.9: The Event Trigger component with a Pointer Enter event

此事件的第一个设置是一个下拉菜单,其中包含“仅运行时”(默认情况下)、“编辑器和运行时”“关闭”选项。在这里我们指定何时可以触发事件。将其设置为“关闭”将使事件永远不会触发。将其设置为“仅运行时”将在玩游戏时触发事件。将其设置为“编辑器和运行时”将在玩游戏时触发事件,但它也会在游戏未处于播放模式时接受编辑器中的触发器。大多数情况下,“仅运行时”足以满足您的要求,因此它是默认设置。

The first setting on this event is a dropdown menu with the Runtime Only (by default), Editor and Runtime, and Off options. This is where we specify when the event can be triggered. Setting this to Off will make the event never trigger. Setting this to Runtime Only will have the event trigger when the game is being played. Setting this to Editor and Runtime will make events trigger when the game is being played, but it also accepts the triggers in the Editor when the game is not in play mode. Most of the time, Runtime Only is sufficient for what you will be doing and hence it is the default.

低于此下拉菜单是一个无(对象)的插槽。您需要从层次结构中将要运行的函数所附加的任何项目拖放到此插槽中。分配完成后,附加到该对象的所有可用组件和脚本的列表将显示在第二个下拉菜单中。您可以将事件触发器所附加的对象拖放到此插槽中,而不限于仅使用其他对象。

Below that dropdown menu is a slot with None (Object) in it. You are to drag from the Hierarchy whichever item the function you want to run is attached to into this slot. Once that is assigned, a list of all the available components and scripts attached to that object will display in the second dropdown menu. You can drag and drop the object the Event Trigger is attached to in this slot and are not restricted to only using other objects.

以下屏幕截图显示了一个添加了事件触发器和Pointer Enter事件类型的Image GameObject 。相同的图像被添加到插槽中,表示查看其自身的组件。当指针进入其Rect Transform时,图像组件的 sprite 属性将更改为foodSpriteSheet_1精灵

The following screenshot shows an Image GameObject with an Event Trigger added to it and the Pointer Enter event type. The same image is added to the slot, signifying to look at the components on itself. The image component’s sprite property is to change to the foodSpriteSheet_1 sprite when the pointer enters its Rect Transform.

图 8.10:带有用于交换精灵的“指针进入”事件的事件触发器组件

图 8.10:带有用于交换精灵的“指针进入”事件的事件触发器组件

Figure 8.10: The Event Trigger component with a Pointer Enter event that swaps a sprite

要查看此事件触发器的运行情况,请播放第 8 章场景。将鼠标悬停在图像上。它最初看起来像一个药水瓶,但当您的鼠标悬停在它上面时,它会变成三角形。

To see this Event Trigger in action, play the Chapter8 scene. Hover your mouse over the image. It will initially look like a potion bottle but will change to a triangle when your mouse hovers over it.

您还可以在附加到对象的脚本中运行函数。例如,下一个屏幕截图显示了相同的图像,但现在也带有指针点击事件。主摄像头附加了一个名为HelloWorld.cs的脚本,其中有一个名为HeyThere() 的函数

You can also run functions within scripts attached to objects. For example, the next screenshot shows the same image but now with a Pointer Click event as well. Main Camera has a script attached to it called HelloWorld.cs with a function called HeyThere().

图 8.11:事件触发器组件,带有触发方法的指针单击事件

图 8.11:事件触发器组件,带有触发方法的指针单击事件

Figure 8.11: The Event Trigger component with Pointer Click event that triggers a method

HeyThere ()函数每当单击右侧的图像时,都会在控制台中打印“Hello world!这是主摄像头在讲话!” 。

The HeyThere() function simply prints Hello world! This is main camera speaking! in the Console whenever the image to the right is clicked.

要从事件触发器组件运行函数,它必须是公共的、返回类型为 void,并且参数不超过一个。

To run a function from the Event Trigger component, it must be public, have a return type of void, and have no more than one parameter.

现在,让我们回顾一下如何编写与 Event Tri 类似的代码gger 组件,通过使用事件输入。

Now, let’s review how we can write code that performs similarly to the Event Trigger component through the use of event inputs.

事件输入

Event inputs

正如事件触发器部分所述,您可能不想使用事件触发器组件,因为事件触发器组件会导致其所附加的对象接收事件触发器部分列出的所有事件。因此,如果您担心性能问题,您将需要另一种方法来接收对象上的事件。

As stated in the Event Trigger section, you may not want to use an Event Trigger component because the Event Trigger component causes the object on which it is attached to receive all the events listed in the Event Trigger section. So, if you are worried about performance issues, you will want an alternate way to receive events on an object.

事件触发器部分中可添加的所有事件类型也可以通过代码添加到对象中,而无需使用事件触发器组件。要使用没有事件触发器组件的事件,您必须从适当的接口派生脚本并了解事件使用的事件数据类的类型

All event types that were available to add in the Event Trigger section can also be added to an object via code without using the Event Trigger component. To use an event without the Event Trigger component, you must derive your script from the appropriate interface and know the type of event data class that the event uses.

接口模板定义了类可以实现的所有必需功能。因此,通过使用接口,您可以使用该接口中定义的任何方法或函数。我将向您展示一些如何执行此操作的示例,但首先,让我们看看可用的事件及其所需的接口。

An interface is a template that defines all the required functionality that a class can implement. So, by using an interface, you can then use any of the methods or functions that have been defined within that interface. I’ll show you some examples of how to do this, but first, let’s look at the available events and their required interfaces.

有三个类可以派生事件数据,分别PointerEventDataAxisEventDataBaseEventData

There are three classes that the event data can be derived from, which are PointerEventData, AxisEventData, and BaseEventData:

  • PointerEventData是包含与指针相关的事件的类
  • PointerEventData is the class that contains events associated with the pointer
  • AxisEventData包含与键盘和游戏手柄相关的事件
  • AxisEventData contains events associated with the keyboard and gamepad
  • BaseEventData包含所有事件类型使用的事件
  • BaseEventData contains events that are used by all event types

有一个第四个事件数据类,AbstractEventData。其他三个类都继承自该类

There is a fourth event data class, AbstractEventData. It is the class from which the other three inherit.

下表列出了StandaloneInputModule可用的事件列表及其所需的接口和事件数据类。为了保持连续性,事件的列出顺序与事件触发器组件中的列出顺序相同:

The list of events available for a StandaloneInputModule along with their required interfaces and event data class are provided in the following chart. The events are listed in the same order they are listed within the Event Trigger component for continuity purposes:

事件

Event

界面

Interface

事件数据类型

Event Data Type

指针进入时

OnPointerEnter

指针输入处理程序

IPointerEnterHandler

指针事件数据

PointerEventData

退出指针

OnPointerExit

退出处理程序

IPointerExitHandler

指针事件数据

PointerEventData

指针向下时

OnPointerDown

指针向下处理程序

IPointerDownHandler

指针事件数据

PointerEventData

指针向上移动

OnPointerUp

指针向上处理程序

IPointerUpHandler

指针事件数据

PointerEventData

指针点击事件

OnPointerClick

指针点击处理程序

IPointerClickHandler

指针事件数据

PointerEventData

拖拽时

OnDrag

拖拽处理器

IdragHandler

指针事件数据

PointerEventData

滴滴

OnDrop

删除处理程序

IdropHandler

指针事件数据

PointerEventData

滚动时

OnScroll

滚动处理程序

IscrollHandler

指针事件数据

PointerEventData

更新选定时

OnUpdateSelected

更新选定处理程序

IUpdateSelectedHandler

基本事件数据

BaseEventData

选定时

OnSelect

选取处理程序

IselectHandler

基本事件数据

BaseEventData

取消选择时

OnDeselect

思想选择处理程序

IdeselectHandler

基本事件数据

BaseEventData

移动

OnMove

移动处理程序

IMoveHandler

轴事件数据

AxisEventData

初始化潜在阻力

OnInitializePotentialDrag

IInitializePotentialDragHandler

IInitializePotentialDragHandler

指针事件数据

PointerEventData

开始拖动时

OnBeginDrag

开始拖动处理程序

IbeginDragHandler

指针事件数据

PointerEventData

拖拽结束

OnEndDrag

结束拖拽处理程序

IEndDragHandler

指针事件数据

PointerEventData

提交时

OnSubmit

提交处理程序

ISubmitHandler

基本事件数据

BaseEventData

取消时

OnCancel

取消处理程序

ICancelHandler

基本事件数据

BaseEventData

表 8.1:各种事件的接口和事件数据类型

Table 8.1: Interfaces and event data types for the various events

写一个类与其中一个事件一起使用时,您将使用以下模板:

To write a class with one of these events, you will use the following template:

使用 UnityEngine;
使用 UnityEngine.UI;
使用 UnityEngine.EventSystems;
公共类类名:MonoBehaviour,接口名称{
     公共无效事件名称事件数据类型名称事件数据){
          //事件触发后会发生什么
     }
}
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
public class ClassName : MonoBehaviour, InterfaceName{
     public void EventName(EventDataTypeName eventData){
          //what happens after event triggers
     }
}

上述代码中突出显示的项目将被上表中的项目替换

The items highlighted in the preceding code will be replaced by the items within the preceding table.

例如,如果您想要实现OnPointerEnter事件,则在将突出显示的代码替换为适当的事件、接口和事件数据类型后,代码将如下所示:

For example, if you wanted to implement an OnPointerEnter event, the code would look as follows after the highlighted code has been replaced with an appropriate event, interface, and event data type:

使用 UnityEngine;
使用 UnityEngine.UI;
使用 UnityEngine.EventSystems;
公共类 ClassName : MonoBehaviour,IPointerEnterHandler
     公共 void OnePointerEnter ( PointerEventData eventData){
         //事件触发后会发生什么
     }
}
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
public class ClassName : MonoBehaviour, IPointerEnterHandler
     public void OnePointerEnter(PointerEventData eventData){
         //what happens after the event triggers
     }
}

你必须包括UnityEngine.EventSystems命名空间,以便使用事件数据编写代码。UnityEngine.UI命名空间是可选的,只有当您还要为UI 对象编写事件时才是必需的。

You must include the UnityEngine.EventSystems namespace to write code with event data. The UnityEngine.UI namespace is optional and is only required if you will also be writing your events for UI objects.

现在我们已经了解了发送和接收事件的各种方式,让我们来看看射线投射器

Now that we’ve reviewed various ways to send and receive events, let’s look at raycasters.

射线投射器

Raycasters

记住事件系统跟踪光线投射以及所有其他我们已经讨论过的事情。光线投射用于通过将光线从用户指针投射到场景中来确定与哪些 UI 元素进行交互。该射线被认为起源于相机的平面,然后向前穿过场景。这条射线击中的任何东西都会得到交互。您可以让射线继续穿过它击中的第一个 UI 元素,也可以停在它击中的第一个 UI 元素处。要让射线停在它击中的第一个 UI 元素处,对象必须阻止光线投射。这将阻止它后面的项目进行交互。接下来,我们将讨论射线投射器的类型秒。

Remember that the Event System keeps track of raycasting along with all the other things we have discussed. Raycasting is used to determine which UI elements are being interacted with by projecting a ray from the user’s pointer into the scene. This ray is considered to originate at the camera’s plane and then proceed forward through the scene. Whatever this ray hits receives an interaction. You can have the ray continue through the first UI element it hits or stop at the first UI element it hits. To get a ray to stop at the first UI element it hits, the object must block raycasting. This will stop items behind it from being interacted with. Next, we’ll discuss the types of raycasters.

图形光线投射器

Graphic Raycaster

画布添加到场景后,它会自动给定一个Graphic Raycaster组件。

When a Canvas is added to the scene, it is automatically given a Graphic Raycaster component.

这是射线投射系统,可让您与该 Canvas 的所有子 UI 对象进行交互。它有三个属性:忽略反转图形阻塞对象阻塞蒙版

This is the raycasting system that will allow you to interact with all UI objects that are children of that Canvas. It has three properties: Ignore Reversed Graphics, Blocking Objects, and Blocking Mask.

“忽略反向图形”切换按钮决定了当 Canvas 中的图形对象面向后方(相对于光线投射器)时,是否可以与其交互。“阻挡对象”“阻挡蒙版”属性允许您分配类型位于画布前方(相机和画布之间)的物体可能会阻挡光线投射帆布。

The Ignore Reversed Graphics toggle determines whether or not graphical objects within the Canvas can be interacted with if they are facing backward (in relation to the raycaster). The Blocking Objects and Blocking Mask properties allow you to assign the types of objects that are in front of the Canvas (between the camera and the Canvas) that can block raycasting to the Canvas.

其他射线投射器

Other Raycasters

如前所述,如果你想使用事件系统非 UI 对象,您必须将 Raycaster 组件附加到场景中的相机。您可以根据使用的是 2D还是 3D,将Physics 2D RaycasterPhysics Raycaster (或两者)添加到相机

As stated earlier, if you want to use the Event System with a non-UI object, you must attach a Raycaster component to a camera within the scene. You can add either a Physics 2D Raycaster or a Physics Raycaster (or both) to your camera based on whether you are using 2D or 3D.

在相机的检查器中,您可以通过选择添加组件|事件|物理 2D 射线投射器来添加物理 2D 射线投射器,也可以通过选择添加组件|事件|物理射线投射器来添加物理射线投射器

From within the camera’s inspector, you can add the Physics 2D Raycaster by selecting Add Component | Event | Physics 2D Raycaster and the Physics Raycaster by selecting Add Component | Event | Physics Raycaster.

两个组件显示如下:

The two components appear as follows:

图 8.12:两种类型的物理射线投射器

图 8.12:两种类型的物理射线投射器

Figure 8.12: The two types of Physics Raycasters

事件掩码属性决定哪些类型的对象可以接收光线投射。

The Event Mask property determines which types of objects can receive raycasting.

如果您尝试将其中一个组件添加到非相机游戏对象 (GameObject),相机组件将自动附加到该游戏对象 (GameObject) 。

If you attempt to add either of these components to a non-camera GameObject, a Camera component will automatically be attached to the GameObject as well.

现在我们已经回顾了可用于为 UI 编程交互的各种系统,让我们看一些例子。

Now that we’ve reviewed the various systems that we can use to program interactions for our UI, let’s look at some examples.

示例

Examples

我们将继续开发前两章中构建的 UI。为了帮助组织项目,请复制您在上一章中创建的Chapter7场景;它将自动命名为Chapter8

We will continue to work on the UI we have been building for the last two chapters. To help organize the project, duplicate the Chapter7 scene that you created in the last chapter; it will automatically be named Chapter8.

笔记

Note

如果您没有完成第 6 章和7 章的示例,但想完成本章中的示例,则可以导入标有第 08 章- 示例 1 - 开始的包。您还可以在第 08 章- 示例 1 -结束中查看已完成的示例 包裹。

If you did not work through the examples for Chapter 6, and Chapter 7, but would like to work through the examples in this chapter, you can import the package labeled Chapter 08 – Examples 1 - Start. You can also view the completed examples in the Chapter 08 – Examples 1 – End package.

通过按键显示和隐藏弹出菜单

Showing and hiding pop-up menus with keypress

到目前为止,我们已经我们计划将两个面板变成弹出窗口:第 6 章中的暂停面板第 7 章中的库存面板。目前,它们都在场景中可见(即使暂停面板隐藏在库存面板后面)。我们希望当我们按下PI时它们会弹出键盘。为了演示目的,我们将以不同的方式访问每个面板的键盘键

So far, we have made two Panels that we plan on turning into popups: the Pause Panel from Chapter 6, and the Inventory Panel from Chapter 7. Right now, they are both visible in the scene (even though Pause Panel is hidden behind the Inventory Panel). We want them to pop up when we press P and I on the keyboard. For demonstration purposes, we’ll access the keyboard keys differently for each Panel.

请记住,这两个面板上都有 Canvas Group 组件。这些组件将使我们能够轻松访问面板的 alpha、intractable 和块raycasts p绳索。

Remember that both of these Panels have Canvas Group components on them. These components will allow us to easily access the Panels’ alpha, intractable, and blocks raycasts properties.

在库存面板中使用 KeyCode

Using KeyCode with the Inventory Panel

让我们库存面板开始。我们希望当按下键盘上的I键时,面板会弹出和关闭。要使用I键显示和隐藏库存面板,请完成以下步骤:

Let’s begin with the Inventory Panel. We want the Panel to pop up and close when the I key is pressed on the keyboard. To make the Inventory Panel appear and disappear with the I key, complete the following steps:

  1. 通过在文件夹的项目视图中单击鼠标右键,然后从弹出面板中选择“创建” | “C# 脚本”,在Assets/Scripts文件夹中创建一个新的 C# 脚本。
  2. Create a new C# script in the Assets/Scripts folder by right-clicking within the Project view of the folder and selecting Create | C# Script from the pop-up Panel.
  3. 将脚本命名为ShowHidePanels.cs,然后双击它打开。
  4. Name the script ShowHidePanels.cs, and then double-click on it to open.
  5. 现在,我们使用一个名为inventoryPanel的公共 CanvasGroup变量来表示Panel。我们使用CanvasGroup变量类型来引用 Panel,因为我们想要访问Canvas Group组件的属性。更新ShowHidePanels脚本以包含以下突出显示的代码行:
    使用System.Collections;
    使用 System.Collections.Generic;
    使用 UnityEngine;
    公共类 ShowHidePanels : MonoBehaviour {
        公共 CanvasGroup 库存面板; 
    }

    CanvasGroup变量类型虽然与 UI 元素一起使用,但不在UnityEngine.UI命名空间中,而是在UnityEngine命名空间中,因此我们目前不需要在脚本中包含UnityEngine.UI命名空间。

  6. Now, let’s use a public CanvasGroup variable called inventoryPanel to represent the Panel. We use a CanvasGroup variable type to reference the Panel since we want to access the properties of the Canvas Group component. Update your ShowHidePanels script to include the following highlighted line of code:
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    public class ShowHidePanels : MonoBehaviour {
        public CanvasGroup inventoryPanel;
    }

    The CanvasGroup variable type, even though it is used with UI elements, is not in the UnityEngine.UI namespace, but the UnityEngine namespace, so we do not need to include the UnityEngine.UI namespace in our script at the moment.

  7. 让我们创建另一个变量来跟踪库存面板是否可见。将以下代码添加到脚本的下一行以初始化变量:
    bool inventoryUp = false;
  8. Let’s create another variable that will keep track of whether or not the Inventory Panel is visible. Add the following code to the next line of the script to initialize the variable:
    bool inventoryUp = false;
  9. 我们将通过调整面板的alphainteractableblocksRaycasts属性来打开和关闭面板。当面板隐藏时,它也不应接受交互或阻止射线广播。因此,让我们创建一个可以调用来执行切换的方法。将以下命名空间添加到您的脚本:
    使用系统;

    将以下方法添加到您的脚本中:

    公共无效TogglePanel(CanvasGroup面板,bool显示)
    {
        Panel.alpha = Convert.ToInt32(显示);
        面板.interactable = 显示;
        面板.blocksRaycasts = 显示;
    }

    正如您所看到的,方法有两个参数。第一个参数是名为Panel 的CanvasGroup ,第二个参数是名为show 的布尔值。当show为false时,它​​将alpha属性设置为0,当showtrue时,它​​将 alpha 属性设置为1。当showfalse时,它​​还会将interactableblocksRaycasts属性设置为false,当showtrue时,​​将设置为true

  10. We will be toggling the Panels on and off by adjusting their alpha, interactable, and blocksRaycasts properties. When the Panel is hidden, it should also not accept interactions or block raycasts. So, let’s create a method that we can call to perform the toggle. Add the following namespace to your script:
    using System;

    Add the following method to your script:

    public void TogglePanel(CanvasGroup Panel, bool show)
    {
        Panel.alpha = Convert.ToInt32(show);
        Panel.interactable = show;
        Panel.blocksRaycasts = show;
    }

    As you can see, the method has two parameters. The first parameter is a CanvasGroup called Panel and the second parameter is a Boolean called show. It will set the alpha property to 0 when show is false and 1 when show is true. It will also set the interactable and blocksRaycasts properties to false when show is false and true when show is true.

  11. 我们想要在场景开始播放时隐藏库存面板。因此,更新Start()函数以包含以下代码:
    无效开始(){
        切换面板(库存面板,inventoryUp);
    }
  12. We want Inventory Panel to be hidden when the scene starts playing. So, update the Start() function to include the following code:
    void Start () {
        TogglePanel(inventoryPanel, inventoryUp);
    }
  13. 现在,我们需要编写代码,每当按下键盘上的I键时都会触发。我们将使用Input.GetKeyDown()函数,以便在按下键时调用该函数。我们还将使用KeyCode.I来引用键盘上的I键。将以下代码添加到Update函数以检查是否按下了I
    无效更新(){
        //库存面板
        如果(输入.GetKeyDown(KeyCode.I)){
        }
    }
  14. Now, we need to write code that triggers whenever the I key on the keyboard is pressed down. We will use the Input.GetKeyDown() function in a way that the function is called the moment the key is pressed down. We will also use KeyCode.I to reference the I key on the keyboard. Add the following code to your Update function to check whether the I key is pressed down:
    void Update () {
        //inventory Panel
        if(Input.GetKeyDown(KeyCode.I)){
        }
    }
  15. 我们想要这个键来禁用和启用面板,因此我们将inventoryUp的值更改为相反的值其当前值的。也就是说,如果它是true,我们将它设置为false,如果它是false,我们将它设置为true。然后,我们将调用TogglePanel()方法。

    将以下突出显示的代码添加到Update()函数中:

    无效更新()
    {
        // 库存面板
        如果(输入.GetKeyDown(KeyCode.I))
        {
            库存上升 = !库存上升;
            切换面板(库存面板,inventoryUp);
        }
    }
  16. We want this key to disable and enable the Panel, so we will change the value of inventoryUp to whatever the opposite of its current value is. That is, if it is true, we will set it to false, if it is false, we will set it to true. Then, we will call the TogglePanel() method.

    Add the following highlighted code to your Update() function:

    void Update()
    {
        // Inventory Panel
        if (Input.GetKeyDown(KeyCode.I))
        {
            inventoryUp = !inventoryUp;
            TogglePanel(inventoryPanel, inventoryUp);
        }
    }
  17. 现在,为了让此代码正常工作,我们需要将其附加到场景中的 GameObject 上。将它附加到哪个 GameObject 上其实并不重要,因为我们使用公共变量来访问Inventory Panel,我们可以通过 Inspector 来分配它。但是,由于我们计划使用此脚本来影响两个面板,因此我想将其添加到Main Camera 。将ShowHidePanels脚本拖放到Main CameraInspector中。您现在应该会在Main Camera上看到以下内容作为组件
  18. Now, for this code to work, we need to attach it to a GameObject within our scene. It really doesn’t matter what GameObject we attach it to, since we used a public variable to access our Inventory Panel, we can assign that via the Inspector. However, since we are planning on using this script to affect both Panels, I want to add it to Main Camera. Drag and drop the ShowHidePanels script into the Inspector of Main Camera. You should now see the following as a component on your Main Camera:
图 8.13:ShowHidePanel.cs 脚本组件

图 8.13:ShowHidePanel.cs 脚本组件

Figure 8.13: The ShowHidePanel.cs script component

  1. 现在,我们需要将Inventory Panel游戏对象分配到标记为Inventory Panel 的插槽中。将Inventory Panel从 Hierarchy拖放到此插槽中:
  2. Now, we need to assign the Inventory Panel GameObject to the slot labeled Inventory Panel. Drag and drop the Inventory Panel from the Hierarchy into this slot:
图 8.14:添加库存面板 ShowHidePanel.cs 脚本组件

图 8.14:添加库存面板 ShowHidePanel.cs 脚本组件

Figure 8.14: Adding the Inventory Panel ShowHidePanel.cs script component

  1. 游戏以确保代码正常运行正确。您应该看到库存面板开始不可见,然后当您按下键盘上的I键时打开和关闭。
  2. Play the game to ensure that the code is working correctly. You should see the inventory Panel start out invisible and then turn on and off as you press the I key on the keyboard.

现在我们已经完成了显示和隐藏库存面板所需的工作,我们可以继续暂停面板

Now that we’ve completed the work needed to show and hide the Inventory Panel, we can move on to the Pause Panel.

将输入管理器与暂停面板结合使用

Using Input Manager with the Pause Panel

现在,让我们开始吧暂停面板也一样。我们的做法与库存面板略有不同。为了确保您知道如何使用输入管理器访问按键,我们将使用输入管理器而不是KeyCode。我们还需要暂停游戏。

Now, let’s do the same thing for the Pause Panel. We’ll do this slightly differently than the Inventory Panel. To make sure that you can see how to access a key with the Input Manager, we’ll use the Input Manager instead of a KeyCode. We also need to actually pause the game.

要使用P键显示暂停面板并暂停游戏,请完成以下步骤:

To display the Pause Panel using the P key and pause the game, complete the following steps:

  1. 首先,我们需要设置输入管理器以包含暂停轴。通过编辑|项目设置|输入管理器打开输入管理器,然后选择单词Axes旁边的箭头来展开轴
  2. First, we need to set up the Input Manager to include a Pause axis. Open the Input Manager with Edit | Project Settings | Input Manager and expand the axes by selecting the arrow next to the word Axes.
  3. 默认情况下,您的项目有 30 个轴。如果您不打算使用它们,可以用新的暂停轴替换其中一个,但我们不妨继续创建一个新的。绝对不要删除提交取消轴,因为我们已经它们在我们的独立输入管理器中被引用。要添加新轴,请将大小更改为31。这将复制列表中的最后一个轴,即“调试水平”,如以下屏幕截图所示
  4. By default, your project has 30 axes. You can replace one of these with the new Pause axis if you aren’t planning on using them, but we might as well just go ahead and make a new one. Definitely don’t delete the Submit and Cancel axes, as we have them being referenced in our Standalone Input Manager. To add a new axis, change the size to 31. This will duplicate the last axis in the list, Debug Horizontal, as shown in the following screenshot:
图 8.15:带有额外轴的输入管理器

图 8.15:带有额外轴的输入管理器

Figure 8.15: The Input Manager with an extra axis

  1. 更改将第二个“调试水平”轴更改为“暂停”轴,方法是将名称更改为“暂停”,将正按钮更改为p,并将其余属性更改为以下内容:
  2. Change the second Debug Horizontal axis to a Pause axis by changing the Name to Pause, the Positive Button to p, and changing the rest of the properties to the following:
图 8.16:添加到输入管理器的暂停轴

图 8.16:添加到输入管理器的暂停轴

Figure 8.16: The Pause axis added to the Input Manager

  1. 现在我们已经设置了暂停轴,我们可以开始编写代码了。让我们定义一些用于暂停面板的变量,类似于我们为库存面板定义变量的方式。在类顶部之前的变量定义下添加以下变量定义:
    公共 CanvasGroup pausePanel; bool pauseUp = false;
  2. Now that we have our Pause axis set up, we can start writing our code. Let’s define some variables to use with the Pause Panel similar to the way we defined variables for Inventory Panel. Add the following variable definitions at the top of your class under your previous variable definitions:
    public CanvasGroup pausePanel;bool pauseUp = false;
  3. 添加按照Start()函数并使暂停面板在启动时不可见
    切换面板(pausePanel,pauseUp);
  4. Add the following to the Start() function and make the Pause Panel invisible at start:
    TogglePanel(pausePanel, pauseUp);
  5. 由于我们在输入管理器中添加了暂停轴,因此我们可以使用Input.GetButtonDown()而不是Input.GetKeyDown() ,就像我们对库存面板所做的那样。我们希望使用GetButtonDown()而不是GetAxis() ,因为我们想要一个只返回一次true 的函数,而不是连续返回。如果它连续返回(使用GetAxis() ),则在按下P键时,面板会闪烁。在Update()函数的末尾添加以下代码。请注意,它与我们用于库存面板的代码非常相似:
    // 暂停面板
    如果(Input.GetButtonDown("暂停")){
         暂停 = !暂停;
         切换面板(pausePanel,pauseUp);
    }
  6. Since we added the Pause axis to our Input Manager, we can use Input.GetButtonDown() instead of Input.GetKeyDown(), like we did with Inventory Panel. We want to use GetButtonDown() rather than GetAxis() because we want a function that will return true once, not continuously. If it returned continuously (using GetAxis()), the Panel would flicker in and out while the P key was being pressed. Add the following code at the end of your Update() function. Note that it’s very similar to the code we used for Inventory Panel:
    // pause Panel
    if(Input.GetButtonDown("Pause")){
         pauseUp = !pauseUp;
         TogglePanel(pausePanel, pauseUp);
    }
  7. 现在我们已将新的公共变量添加到脚本中,它应该显示在主摄像头检查器中。将暂停面板从层次结构拖放到暂停面板插槽。
  8. Now that we’ve added new public variables to our script, it should be showing up in the Inspector of Main Camera. Drag and drop the Pause Panel from the Hierarchy to the Pause Panel slot.
图 8.17:添加了暂停面板的 ShowHidePanel.cs 脚本组件

图 8.17:添加了暂停面板的 ShowHidePanel.cs 脚本组件

Figure 8.17: The ShowHidePanel.cs script component with the Pause Panel added

  1. 现在,玩游戏并观察暂停面板在你按下键盘上的P键。
  2. Now, play the game and watch the Pause Panel become visible and invisible when you press the P key on the keyboard.

接下来我们将学习关于暂停游戏。

Next, we’ll learn about pausing the game.

暂停游戏

Pausing the game

游戏现在实际上并没有暂停。如果我们在场景中运行动画或事件,即使暂停面板打开,它们也会继续运行。暂停游戏的一个非常简单的方法是操纵游戏的时间刻度。如果时间刻度设置为1,时间将照常运行。如果时间刻度设置为0,游戏内的时间将暂停。

The game doesn’t actually pause right now. If we had animations or events running in the scene, they would continue to run even with the Pause Panel up. A really easy way to pause a game is to manipulate the time scale of the game. If the time scale is set to 1, time will run as it normally does. If the time scale is set to 0, the time within the game will pause.

此外,我们当前的设置无法像预期的那样正常工作。库存面板暂停面板可以同时显示。如果库存面板已打开,则暂停面板将被其覆盖,因为它正在其后面渲染。此外,当游戏暂停时可以激活库存面板

Also, our current setup doesn’t quite work as a pause menu would be expected to. Inventory Panel and Pause Panel can be displayed at the same time. If Inventory Panel is up, the Pause Panel is covered up by it since it is rendering behind it. Also, the Inventory Panel can be activated when the game is paused.

我们需要暂停游戏的时间尺度,更改面板渲染的顺序,并在游戏暂停时禁用功能,以使暂停面板正常运行。要创建正常运行的暂停面板,请完成以下步骤:

We’ll need to pause the time scale of our game, change the order that our Panels render, and disable functionality when the game is paused to have a Pause Panel that functions properly. To create a properly functioning Pause Panel, complete the following steps:

  1. 将以下内容添加到ShowHidePanels脚本的Update()函数中,以暂停游戏时间:
    // 暂停面板
    如果(Input.GetButtonDown("暂停")){
         暂停 = !暂停;
         切换面板(pausePanel,pauseUp);
         时间.timeScale = Convert.ToInt32(pauseUp); 
    }
  2. Add the following to the Update() function to the ShowHidePanels script to pause the time in the game:
    // pause Panel
    if(Input.GetButtonDown("Pause")){
         pauseUp = !pauseUp;
         TogglePanel(pausePanel, pauseUp);
         Time.timeScale = Convert.ToInt32(pauseUp);
    }
  3. 现在,让我们处理暂停面板位于库存面板后面的事实。这是一个简单的修复方法。只需将暂停面板拖到库存面板下方,即可更改它们在层次结构中的顺序。层次结构中较低列出的项目将在场景中呈现在它上面列出的项目的顶部。现在,暂停面板将位于场景中的库存面板上方:
  4. Now, let’s deal with the fact that Pause Panel is behind the Inventory Panel. This is an easy fix. Simply change their order in the Hierarchy by dragging the Pause Panel below the Inventory Panel. The items that are listed lower in the Hierarchy render on top of the ones listed above it within the scene. Now, the Pause Panel will be above the Inventory Panel in the scene:
图 8.18:弹出画布的子项

图 8.18:弹出画布的子项

Figure 8.18: The children of the Popup Canvas

  1. 剩下要做的就是禁用库存面板在暂停面板启动时出现和消失的功能。调整检查是否按下I键的if语句,以检查pauseUp是否为 false,如下所示:
    如果(Input.GetKeyDown(KeyCode.I)&&!pauseUp){
  2. The only thing left to do is to disable the ability of the Inventory Panel to appear and disappear if the Pause Panel is up. Adjust the if statement that checks for the I key being pressed to also check whether pauseUp is false, like so:
    if(Input.GetKeyDown(KeyCode.I) && !pauseUp){
  3. 现在玩游戏,你会发现当游戏暂停时,库存面板无法激活或停用。如果在暂停面板已经启动时激活了库存面板,则在游戏恢复暂停之前无法停用它。
  4. Play the game now and see that when the game is paused, Inventory Panel cannot be activated or deactivated. If the Inventory Panel is activated when the Pause Panel is already up, it cannot be deactivated until after the game is unpaused.

重要的是请记住,当您有暂停面板时,需要关闭其他事件。将时间刻度设置为0不会阻止其他事件发生的能力;它实际上只会停止动画和您可能显示的任何使用时间刻度的时钟。因此,我们需要确保我们编程的任何其他事件都已关闭当游戏暂停时。

It’s important to remember that when you have a Pause Panel, other events need to be turned off. Setting the timescale to 0 does not stop the ability for other events to occur; it only really stops animations and any clocks you may have displayed that use the time scale. So, we will need to ensure that any other event we program is turned off when the game is paused.

拖放库存物品

Dragging and dropping inventory items

我们有一个可以显示和隐藏的库存面板和一个HUD库存。我希望能够将对象从较大的库存面板拖到我们在上一章中创建的较小的 HUD 库存(称为右下面板)中

We have an Inventory Panel that can be displayed and hidden and a HUD inventory. I want to be able to drag objects from my larger Inventory Panel to my smaller HUD inventory called Bottom Right Panel that we created in the previous chapter.

为了让事情变得简单一点,让我们禁用本章前面添加到主摄像头的ShowHidePanels脚本。您可以通过取消选择主摄像头上脚本组件旁边的复选框来执行此操作:

To make things a little easier for ourselves, let’s disable the ShowHidePanels script that we added to the Main Camera earlier in this chapter. You can do this by deselecting the checkbox next to the script’s component on the Main Camera:

图 8.19:禁用 ShowHidePanel.cs 脚本组件

图 8.19:禁用 ShowHidePanel.cs 脚本组件

Figure 8.19: Disabling the ShowHidePanel.cs script component

我们还要禁用“暂停面板”,这样它就不会妨碍我们了。方法是取消选中“暂停面板”检查器中其名称旁边的复选框。

Let’s also disable Pause Panel so that it will not be in our way. Do this by deselecting the checkbox next to the name of the Pause Panel in its Inspector.

现在,我们的面板将保持可见,使我们更容易调试我们即将编写的代码。

Now, our Panel will stay visible, making it easier for us to debug the code we’re about to write.

制作拖放机制的方法有很多种。为了确保本章提供了如何使用事件触发器组件的示例,我们将使用它编写一个拖放机制。要为库存面板右下面板创建拖放机制,请完成以下步骤:

There are quite a few different ways to make a drag and drop mechanic. To ensure that this chapter provides an example of how to use the Event Trigger component, we will write a drag and drop mechanic utilizing it. To create a drag and drop mechanic for the Inventory Panel and Bottom Right Panel, complete the following steps:

  1. 在Assets/Scripts文件夹中创建一个名为DragAndDrop.cs的新 C# 脚本并打开它。
  2. Create a new C# script in the Assets/Scripts folder called DragAndDrop.cs and open it.
  3. 我们将在此脚本中引用 UI 元素,因此使用以下命令将UnityEngine.UI命名空间添加到脚本顶部
    使用 UnityEngine.UI;
  4. We will be referencing UI elements in this script, so add the UnityEngine.UI namespace to the top of the script with this:
    using UnityEngine.UI;
  5. 我们只需要向此脚本添加两个变量:一个变量代表被拖动的游戏对象,另一个变量代表将拖动项目所在的 Canvas。将以下公共变量添加到类的顶部:
    公共游戏对象拖拽项;¶公共画布拖拽画布;
  6. We only need to add two variables to this script: one will represent the GameObject being dragged, and the other represents the Canvas that the items will be dragged on. Add the following public variables to the top of the class:
    public GameObject dragItem;¶public Canvas dragCanvas;
  7. 在编写更多代码之前,让我们回到编辑器并做一些准备工作。将DragAndDrop.cs脚本拖到主摄像头检查器中,将其作为组件附加:
    图 8.20:主摄像头的组件

    图 8.20:主摄像头的组件

    我选择创建一个附加到主摄像头而不是单个库存物品的脚本,以减少重复该脚本的需要

  8. Before we write any more code, let’s go back to the Editor and do a bit more prep work. Drag the DragAndDrop.cs script to the Inspector of the Main Camera to attach it as a component:

    Figure 8.20: The components of the Main Camera

    I’ve chosen to create a script that attaches to the Main Camera rather than the individual inventory items to reduce the need to duplicate this script.

  1. 现在,创建从层次结构菜单中选择+ | UI | Canvas创建一个新 UI Canvas 。将新 Canvas 命名为Drag Canvas
  2. Now, create a new UI Canvas by selecting + | UI | Canvas from the Hierarchy menu. Name the new Canvas Drag Canvas.
  3. 选择HUD Canvas并通过选择其右上角的三个点(又名“烤肉串”)设置并选择Copy Component来复制其Canvas Scalar组件
  4. Select HUD Canvas and copy its Canvas Scalar component by selecting the settings three dots (aka “the kabob”) in its top-right corner and selecting Copy Component.
  5. 重新选择Drag Canvas ,并通过选择其右上角的三个点并选择Paste Component Values,将复制的Canvas Scaler属性粘贴到其Canvas Scalar组件中

    一旦完成后,它应该具有以下值:

  6. Reselect Drag Canvas and paste the copied Canvas Scaler properties to its Canvas Scalar component by selecting the three dots in its top-right corner and selecting Paste Component Values.

    Once that is done, it should have the following values:

图 8.21:Drag Canvas 上的 Canvas Scaler 组件

图 8.21:Drag Canvas 上的 Canvas Scaler 组件

Figure 8.21: The Canvas Scaler component on the Drag Canvas

  1. 将Drag Canvas的 Canvas 组件上的Sort Order属性设置为1。这将导致拖动画布上的所有内容都渲染在所有其他画布的前面,因为其他画布的排序顺序 0
  2. Set the Sort Order property on the Drag Canvas’ Canvas component to 1. This will cause anything that is on the Drag Canvas to render in front of all other Canvases since the other Canvases have a Sort Order of 0:
图 8.22:更新 Drag Canvas 的 Canvas 组件上的排序顺序

图 8.22:更新 Drag Canvas 的 Canvas 组件上的排序顺序

Figure 8.22: Updating the Sort Order on the Drag Canvas’ Canvas component

  1. Drag Canvas从层次结构拖放到主摄像机上的DragAndDrop脚本组件上的Drag Canvas插槽中
  2. Drag and drop the Drag Canvas from the Hierarchy into the Drag Canvas slot on the DragAndDrop script component on the Main Camera:
图 8.23:拖放组件

图 8.23:拖放组件

Figure 8.23: The Drag and Drop component

  1. 重新打开DragAndDrop脚本。创建一个名为StartDrag()的新函数,如下所示:
    公共无效StartDrag(GameObject selectedObject){
        dragItem = Instantiate(selectedObject,Input.mousePosition,selectedObject.transform.rotation)作为GameObject;
        DragItem.transform.SetParent(dragCanvas.transform);
        拖拽em.GetComponent<Image>().SetNativeSize();
        拖动项目.transform.localScale = 1.1f * 拖动项目.transform.localScale;
    }

    此功能将在拖动开始时调用。它接受 GameObject 作为参数,然后在鼠标位置创建它的新实例。然后移动它,使其成为dragCanvas的子项。最后,它将 Image 组件上的精灵的大小设置为原始大小。这会将 Image 的 Rect Transform 的比例重置为其精灵的原始像素大小。(有关设置原始大小的更多信息,请参阅第 12 章)。最后一行使图像比其原始尺寸大 10%。

  2. Reopen the DragAndDrop script. Create a new function called StartDrag(), as follows:
    public void StartDrag(GameObject selectedObject){
        dragItem = Instantiate(selectedObject, Input.mousePosition, selectedObject.transform.rotation) as GameObject;
        dragItem.transform.SetParent(dragCanvas.transform);
        dragItem.GetComponent<Image>().SetNativeSize();
        dragItem.transform.localScale = 1.1f * dragItem.transform.localScale;
    }

    This function will be called when a drag begins. It accepts a GameObject as a parameter and then creates a new instance of it at the position of the mouse. It then moves it so that it is a child of dragCanvas. Lastly, it sets the size of the sprite on the Image component to native size. This resets the scale of the Image’s Rect Transform to its sprite’s original pixel size. (Refer to Chapter 12 for more on Set Native Size). The last line makes the image 10% bigger than its native size.

笔记

Note

在我们连接BeginDragDrag事件之后,如果你注释掉将大小设置为本机的代码行,你会看到图像实际上并没有在场景中渲染,因为它的比例与布局组内的原始游戏对象相比“古怪

After we hook up our BeginDrag and Drag events, if you comment out the line of code that sets the size to native, you›ll see that the Image does not actually render in the scene, because its scale is «wacky» from the original GameObject being within a Layout Group.

  1. 现在,创建一个名为Drag()的新函数,如下所示:
    公共无效拖动(){
         DragItem.transform.position = Input.mousePosition;
    }

    当对象被拖拽时,该函数会被调用。对象被拖拽时,它会随鼠标保持位置不变。

  2. Now, create a new function called Drag(), as follows:
    public void Drag(){
         dragItem.transform.position = Input.mousePosition;
    }

    This function will be called when an object is being dragged. While the object is dragged, it will keep position with the mouse.

  3. 返回编辑器。我们暂时只将事件挂接到库存面板中的第一个对象。选择库存面板中的第一个食物图像
  4. Return to the Editor. We will just hook the events to the first object in the Inventory Panel for now. Select the first Food image in the Inventory Panel:
图 8.24:选择食物游戏对象

图 8.24:选择食物游戏对象

Figure 8.24: Selecting the Food GameObject

  1. 通过选择添加组件|事件|检查器事件触发器,向食物图像添加新的事件触发器组件
  2. Add a new Event Trigger component to the Food Image by selecting Add Component | Event | Event Trigger within its Inspector:
图 8.25:带有事件触发器组件的食物游戏对象

图 8.25:带有事件触发器组件的食物游戏对象

Figure 8.25: The Food GameObject with the Event Trigger component

  1. 现在,添加一个Begin Drag事件通过选择添加新事件类型| BeginDrag添加新事件类型| Drag,将类型和Drag事件类型添加到事件触发器列表中
  2. Now, add a Begin Drag event type and a Drag event type to the Event Trigger list by selecting Add New Event Type | BeginDrag and Add New Event Type | Drag:
图 8.26:具有两个事件的事件触发器组件

图 8.26:具有两个事件的事件触发器组件

Figure 8.26: The Event Trigger component with two events

  1. 现在,我们通过选择“开始拖动”区域右下角的加号,将向“开始拖动”列表添加一个操作
  2. Now, we will add an action to the Begin Drag list by selecting the plus sign at the bottom-right corner of the Begin Drag area:
图 8.27:添加开始拖动事件

图 8.27:添加开始拖动事件

Figure 8.27: Adding a Begin Drag event

  1. 主摄像机拖入对象槽:
  2. Drag the Main Camera into the object slot:
图 8.28:使用相机更新 Begin Drag 事件

图 8.28:使用相机更新 Begin Drag 事件

Figure 8.28: Updating the Begin Drag event with the camera

  1. 函数下拉列表现在变得难以处理。展开函数下拉列表可查看附加到主摄像头的功能、组件等列表。找到DragAndDrop脚本,然后找到StartDrag ( GameObject )函数:
    图 8.29:添加 StartDrag 方法

    图 8.29:添加 StartDrag 方法

    完成此操作后,您将看到以下内容:

    图 8.30:添加 StartDrag 方法

    图 8.30:添加 StartDrag 方法

  2. The function dropdown list is now intractable. Expand the function dropdown list to see the list of functions, components, and such attached to the Main Camera. Find the DragAndDrop script and then the StartDrag (GameObject) function:

    Figure 8.29: Adding the StartDrag method

    Once you have done so, you should see the following:

    Figure 8.30: Adding the StartDrag method

  1. 现在,我们需要分配 GameObject 参数。将此Event Trigger组件附加的食物图像拖放到参数槽中。
  2. Now, we need to assign the GameObject parameter. Drag and drop the Food Image that this Event Trigger component is attached to into the parameter slot.
图 8.31:更新 StartDrag 方法

图 8.31:更新 StartDrag 方法

Figure 8.31: Updating the StartDrag method

  1. 现在,以类似的方式设置Drag事件列表,使其看起来像这样:
  2. Now, set up the Drag event list similarly so that it looks like this:
图 8.32:添加 Drag 方法

图 8.32:添加 Drag 方法

Figure 8.32: Adding the Drag method

  1. 如果您玩游戏,您现在应该能够将第一个插槽中的橙子拖出其插槽。
    图 8.33:从库存中拖出橙子

    图 8.33:从库存中拖出橙子

    你会看到,在在层次结构中,有一个名为Food(Clone)的新 GameObject,它是Drag Canvas的子对象。这是开始拖动时创建的橙子

    图 8.34:拖拽画布中被拖拽的项目

    图 8.34:拖拽画布中被拖拽的项目

    此时,您可以根据需要制作任意数量的克隆。不过,稍后我们将使Drag Canvas中一次只有一个克隆

  2. If you play the game, you should now be able to drag the orange in the first slot out of its slot.

    Figure 8.33: Dragging the orange from the inventory

    You’ll see, in the Hierarchy, there is a new GameObject called Food(Clone) that is a child of the Drag Canvas. This is the orange that gets created when you begin dragging.

    Figure 8.34: The item being dragged in the Drag Canvas

    At this point, you can actually make as many of these clones as you want. In a moment, however, we will make it so that there is only one clone in the Drag Canvas at a time.

  1. 返回DragAndDrop脚本并创建一个名为StopDrag() 的新函数,如下所示:
    公共无效StopDrag(){
         销毁(dragItem);
    }

    一旦不再拖动,此代码就会销毁Food(Clone)游戏对象。

  2. Go back to the DragAndDrop script and create a new function called StopDrag(), as follows:
    public void StopDrag(){
         Destroy(dragItem);
    }

    This code will destroy the Food(Clone) GameObject once it is no longer being dragged.

  3. 返回编辑器并在库存面板中重新选择食物图像。通过选择添加新事件类型| EndDrag,为其事件触发器组件指定EndDrag事件类型。它将自动将DragAndDrop脚本中的Drag()函数分配给此事件,因为这是最后选择的函数:
  4. Go back to the Editor and reselect the Food Image in Inventory Panel. Give its Event Trigger component an EndDrag event type by selecting Add New Event Type | EndDrag. It will automatically assign the Drag() function from the DragAndDrop script to this event since that was the last selected function:
图 8.35:带有 Drag 方法的 End Drag 事件

图 8.35:带有 Drag 方法的 End Drag 事件

Figure 8.35: The End Drag event with the Drag method

  1. 将函数下拉菜单中的Drag()函数替换为StopDrag()函数:
  2. Replace the Drag() function in the function dropdown with the StopDrag() function:
图 8.36:带有 StopDrag 方法的 End Drag 事件

图 8.36:带有 StopDrag 方法的 End Drag 事件

Figure 8.36: The End Drag event with the StopDrag method

  1. 玩游戏,你会看到橙子现在可以从插槽中拖出,当你松开鼠标时,橙子会被破坏。这样你就无法从插槽中拖出一堆橙子了。
  2. Play the game, and you will see that the orange can now be dragged out of its slot, and when you release the mouse, it is destroyed. This stops you from being able to drag a bunch of oranges from this slot.
  3. 返回DragAndDrop脚本并创建一个名为Drop() 的新函数,如下所示
    公共无效删除(图像dropSlot){
         游戏对象 droppedItem = dragCanvas.transform.GetChild(0).gameObject;
         dropSlot.sprite = dropItem.GetComponent<Image>().sprite;
    }

    函数接受一个Image作为参数。此 Image 将是接收放置的插槽的 Image 组件。函数的第一行找到 dragCanvas 的第一个子项在位置0处),然后将其 Image 的精灵分配给dropSlot的精灵。由于我们已经设置了StopDrag()函数以在停止拖动后销毁被拖动的对象,因此我们不必担心Drag Canvas GameObject 有多个子项,这使其成为查找被拖动对象的最简单方法

  4. Go back to the DragAndDrop script and create a new function called Drop(), as shown in the following:
    public void Drop(Image dropSlot){
         GameObject droppedItem = dragCanvas.transform.GetChild(0).gameObject;
         dropSlot.sprite = droppedItem.GetComponent<Image>().sprite;
    }

    This function accepts an Image as a parameter. This Image will be the Image component of the slot that will receive a drop. The first line of the function finds the first child of the dragCanvas (at position 0) and then assigns its Image’s sprite to the sprite of the dropSlot. Since we have set up the StopDrag() function to destroy the object being dragged once it stops dragging, we don’t have to worry about there being more than one child of the Drag Canvas GameObject, making this the easiest way to find the object being dragged.

  5. 返回编辑器并选择右下方面板中的第二个食物图像
    图 8.37:选择正确的食物

    图 8.37:选择正确的食物

    我们使用第二张食物图像,而不是第一张,因为第一张里面已经有一个橙子了,而且很难看出我们的脚本在那个位置起作用了。

  6. Go back to the Editor and select the second Food Image in the Bottom Right Panel:

    Figure 8.37: Selecting the correct Food item

    We’re using the second Food Image, rather than the first, because the first already has an orange in it, and it will be hard to tell that our script worked in that slot.

  1. 通过选择添加组件|事件|检查器中的事件触发器,食物图像添加新的事件触发器组件。
  2. Add a new Event Trigger component to the Food Image by selecting Add Component | Event | Event Trigger within its Inspector.
  3. 通过选择添加新事件类型| Drop,事件触发器组件添加Drop事件类型
  4. Add a Drop event type to the Event Trigger component by selecting Add New Event Type | Drop:
图 8.38:Drop 事件

图 8.38:Drop 事件

Figure 8.38: The Drop event

  1. 添加新的使用+ 号将操作添加到列表中
  2. Add a new action to the list with the + sign.
  3. 主摄像头拖到对象槽中,然后从DragAndDrop脚本中选择Drop()函数
  4. Drag Main Camera to the object slot and select the Drop() function from the DragAndDrop script:
图 8.39:Drop 事件及其方法

图 8.39:Drop 事件及其方法

Figure 8.39: The Drop event with its method populated

  1. 现在,将此事件触发器组件附加的食物图像拖入参数槽中:
  2. Now, drag the Food Image that this Event Trigger component is attached to into the parameter slot:
图 8.40:具有正确参数的 Drop 事件

图 8.40:具有正确参数的 Drop 事件

Figure 8.40: The Drop event with the correct parameter

  1. 玩游戏,当你将橙子拖入香蕉上方的插槽时,Drop()函数似乎没有触发。这是因为被拖拽的橙子阻碍了光线投射到达香蕉,所以香蕉从来不会认为有任何东西掉落在它上面。这是一个简单的解决方法。在DragAndDrop脚本中的StartDrag()函数末尾添加以下代码行,这样光线投射就不会再被橙子阻挡了:
    DragItem.GetComponent<Image>().raycastTarget = false;
  2. Play the game and when you drag the orange into the slot over the banana, the Drop() function doesn’t appear to trigger. This is because the orange being dragged is blocking the raycasting from reaching the banana, so the banana never thinks anything is dropped on it. This is an easy fix. Add the following line of code to the end of the StartDrag() function in the DragAndDrop script so that the raycast won’t be blocked by the orange anymore:
    dragItem.GetComponent<Image>().raycastTarget = false;
  3. 游戏,现在您应该能够将橙子从库存面板的第一个插槽拖到右下角面板的第二个插槽
  4. Play the game, and you should now be able to drag the orange from the first slot of the Inventory Panel to the second slot of Bottom Right Panel.
  5. 拖放功能现已完成;我们只需将功能添加到其他插槽。让我们首先将拖放事件添加到其他库存面板 项目。

    复制我们将事件挂接到的库存面板中的食品项目的事件触发器组件,使用组件右上角的三个点。

  6. The functionality of drag and drop is now complete; we just need to add the functionality to the other slots. Let’s add the drag events to the other Inventory Panel items first.

    Copy the Event Trigger component of the Food Item in the Inventory Panel that we hook the events up to, using the three dots in the component’s top-right corner.

  7. 按住Ctrl 键并单击库存面板中的所有其他食物图像以将其选中
  8. Select all the other Food Images in the Inventory Panel by clicking on them while holding Ctrl.
  9. 现在,在仍然选择所有八张食物图像的情况下,单击图像组件上的三个点,然后选择将组件粘贴为新组件。现在,每张食物图像都应该具有包含所有适当事件的事件触发器组件
  10. Now, with all eight Food Images still selected, click on the three dots on the Image component and select Paste Component as New. Each of the Food Images should now have the Event Trigger component with all the appropriate events.
  11. 我们还没有处理完这些其他库存物品。我们需要选择每个物品并将其拖入其组件的BeginDrag事件类型的参数中。否则,其他八个食物物品中的每一个都会拖出橙子而不是适当的食物物品,因为原始橙子被分配到该插槽。现在就这么做。
  12. We’re not done with these other inventory items yet. We need to select each one and drag it into the parameter of the BeginDrag event type for its component. Otherwise, each of these other eight Food items will drag out oranges instead of the appropriate Food item, because the original orange is assigned to that slot. Do so now.
  13. 继续之前,玩游戏并确保库存面板中的每个库存物品都拖出相应的图像。
  14. Before continuing, play the game and ensure that each inventory item in Inventory Panel drags out the appropriate image.
  15. 现在,我们将右下面板中第二张食物图像的放置事件复制到面板内的所有其他食物图像。从右下面板中的第二张食物图像复制事件触发器组件
  16. Now, we will copy the drop events from the second Food Image image in the Bottom Right Panel to all the other Food Images within the Panel. Copy the Event Trigger component from the second Food Image in the Bottom Right Panel.
  17. 选择按住Ctrl 键,在右下方面板单击其他四个食物图像
  18. Select the other four Food Images in the Bottom Right Panel while holding down Ctrl.
  19. 在仍然选择上图所示的每个食物图像的情况下,将组件粘贴为检查器中的新组件。
  20. With each of the Food Images shown in the preceding screenshot still selected, paste the component as new in the Inspector.
  21. 现在,选择每个新的食物图像并将每个图像分配给其事件触发器组件内的参数槽
  22. Now, select each of the new Food Images and assign each to the parameter slot within their Event Trigger component.
  23. 玩游戏并确保当食物放入图像槽时,正确的图像槽会被更改。
  24. Play the game and ensure that the correct image slot is changed when a food item is dropped into it.
  25. 现在拖放代码已完成,请重新启用主摄像头上的ShowHidePanels脚本并重新启用暂停面板
  26. Now that the drag and drop code is done, re-enable the ShowHidePanels script on the Main Camera and re-enable the Pause Panel.

这就是拖放代码。目前,暂停面板会阻止对库存面板内物品的射线投射,因此您不必担心在游戏暂停时禁用这些事件。但是,如果您最终要更改布局,则需要在执行任务之前检查ShowHidePanels中的pauseUp变量是否为false 。

That’s it for the drag and drop code. Currently, the Pause Panel blocks the raycast on the items within the Inventory Panel, so you don’t have to worry about disabling these events when the game is paused. However, if you end up changing the layout, you will want to do so by checking whether the pauseUp variable in ShowHidePanels is false before performing the tasks.

如果您想允许对象来回移动(从两个面板拖动并放入两个面板),您需要做的就是将相应的组件复制到相对的面板!

If you want to allow the objects to go back and forth (drag from both Panels and drop in both Panels), all you have to do is copy the appropriate component to the opposite Panels!

您可能还想将重复的 UI 元素制作成预制件,以便在开发过程中节省一些时间或以编程方式实例化它们。

You might also want to make the repeated UI elements prefabs so that you can save yourself some time during development or instantiate them programmatically.

本章中还有很多例子我想介绍,但我不能让这一章占据整本书的页数!您将在接下来的章节中看到更多关于如何使用事件系统的示例,所以不用担心;这不是您将看到的最后一个代码示例

There are so many more examples I would love to cover in this chapter, but I can’t make this chapter take up the entire page count of the book! You’ll see more examples of how to use the Event System in the upcoming chapters, so don’t worry; this isn’t the last code example you will see.

使用鼠标和多点触控输入进行平移和缩放

Pan and zoom with mouse and multi-touch input

最后本章中我想介绍的示例是如何使用双指触摸来平移相机,以及捏合来缩放。我们还将实现左键单击平移和滚轮缩放,以便您可以轻松地在计算机上测试它(当您没有多点触控输入时)。下图显示了我们将要实现的内容。

The last example I want to cover in this chapter is how to pan the camera with a two-finger touch and pinch to zoom. We will also implement a left-click pan and scroll wheel zoom so that you can easily test it on your computer (when you don’t have multi-touch input). The following image shows what we will be implementing.

图 8.41:平移和缩放代码工作示例

图 8.41:平移和缩放代码工作示例

Figure 8.41: Demonstration of the pan and zoom code working

要实现相机的平移和缩放,请完成以下步骤:

To implement a pan and zoom on the camera, complete the following steps:

  1. 为了能够真正看到平移和缩放的效果,我想在场景中填充一些项目。让我们从创建一个预制件开始,我们将在背景中放置多次。右键单击Assets文件夹并选择Create | Folder ,在项目中创建一个Prefabs文件夹。将新文件夹命名为Prefabs
  2. So that I can actually see the effects of the pan and zoom, I want to populate some items in the scene. Let’s start by creating a prefab that we will place multiple times in the background. Create a Prefabs folder in your project by right-clicking on the Assets folder and selecting Create | Folder. Name the new folder Prefabs.
  3. 将精灵拖到场景中。我选择了foodSpriteSheet中的第一个宝石, 名为foodSpriteSheet_1
  4. Drag a sprite to the scene. I chose the first gem in the foodSpriteSheet called foodSpriteSheet_1.
  5. 确保精灵的Transform组件位于原点。如果不是,请选择组件右上角的三个点,然后选择Reset
  6. Ensure that the Transform component of the sprite is positioned at the origin. If it is not, select the three dots in the top-right corner of the component and then select Reset.
  7. 将精灵重命名为Tile
  8. Rename the sprite Tile.
  9. 现在,将精灵从 Hierarchy 拖到Prefabs文件夹中。这将创建一个名为Tile的预制件。
  10. Now, drag the sprite from the Hierarchy into your Prefabs folder. This will create a prefab called Tile.
  11. 我们不再需要场景中的预制件,因此继续将其删除。
  12. We no longer need the prefab in the scene so go ahead and delete it.
  13. 在层次结构中选择+ |创建空对象,创建一个空的游戏对象。我们将使用它来保存实例化图块的代码。
  14. Create an empty GameObject by selecting + | Create Empty in the Hierarchy. We will use this to hold the code that instantiates our tiles.
  15. 重命名将这个空的 GameObject 添加到Tile Maker中。
  16. Rename this empty GameObject to Tile Maker.
  17. 在本书的源文件中,您将找到三个脚本,分别名为Tile.csTileMaker.csCameraHandle.cs。将它们导入到项目Scripts文件夹中。
  18. In the book’s source files, you will find three scripts called Tile.cs, TileMaker.cs, and CameraHandle.cs. Import them into the Scripts folder of your project.
  19. Tile.cs脚本附加到Tile预制件。此脚本将用于使任何实例化的Tile预制件具有随机精灵。
  20. Attach the Tile.cs script to the Tile prefab. This script will be used to make any instantiated Tile prefab have a random sprite.
  21. 由于Tile.cs脚本对于示例来说并不是很重要,因此我不会查看代码,但我会指出,possibleSprites列表包含图块可以更改为的所有精灵。将所有看起来像宝石的精灵添加到此列表中。您应该看到类似以下内容
  22. Since the Tile.cs script is not really essential to the example, I won’t review the code, but I will point out that the possibleSprites list contains all the sprites that the tile can change to. Add all the sprites that look like gems to this list. You should see something like the following
图 8.42:图块可能的精灵

图 8.42:图块可能的精灵

Figure 8.42: The possible sprites for the tile

  1. 现在TileMaker.cs脚本附加到Tile Maker GameObject。
  2. Now attach the TileMaker.cs script to the Tile Maker GameObject.
  3. Tile预制件拖入Tile Prefab插槽,并设置其他值如下:
    图 8.43:TileMaker.cs 脚本组件

    图 8.43:TileMaker.cs 脚本组件

    这将使总共500 个图块在25列和20行中实例化。

  4. Drag the Tile prefab into the Tile Prefab slot and set the other values as follows:

    Figure 8.43: The TileMaker.cs script component

    This will make a total of 500 tiles instantiate in 25 columns and 20 rows.

  1. 玩游戏,你会看到场景中出现一堆随机的宝石。
  2. Play the game and you should see a bunch of random gems appear in your scene.
图 8.44:场景中的宝石

图 8.44:场景中的宝石

Figure 8.44: The gems in the scene

  1. 现在,让我们连接平移和缩放脚本。将CameraHandler.cs脚本附加到主摄像头
  2. Now, let’s hook up the pan and zoom script. Attach the CameraHandler.cs script to the Main Camera.
  3. 在检查代码之前,让我们先将正确的变量添加到组件中。将值调整为以下内容:
    图 8.45:CameraHandler.cs 脚本组件

    图 8.45:CameraHandler.cs 脚本组件

    各种属性增加了相机平移距离、变焦倍数以及平移和变焦速度的界限。

  4. Before we review the code, let’s add the correct variables to the component. Adjust the values to the following:

    Figure 8.45: The CameraHandler.cs script component

    The various properties add bounds to how far the camera can pan, how much it can zoom, and how quickly it pans and zooms.

  1. 现在,让我们回顾一下代码。代码以两种方式处理平移和缩放:一种是使用鼠标输入,另一种是使用触摸输入。您会发现,当通过 Unity 编辑器远程播放触摸设备时,它还具有特殊条件

    这段代码的大部分内容是矢量数学,我将留给您自己查看。我想重点介绍的代码部分是与本章相关的部分,特别是涉及InputTouch 的部分。首先,让我们看一下HandleMouse()方法。我已突出显示相关部分:

    无效 HandleMouse() {
         如果(输入.GetMouseButtonDown(0)){
              最后平移位置 =输入.鼠标位置;
         } 否则,如果(输入.GetMouseButton(0)){
              PanCamera(Input.mousePosition);
         }
         float scroll = Input.GetAxis("鼠标滚轮") ;
         缩放相机(滚动,缩放速度鼠标);
    }

    注意它使用Input.GetMouseButtonDown(0)来查看鼠标左键当前是否被按下,使用Input.GetMouseButton(0)来查看鼠标按钮是否已被单击,并使用Input.mousePosition来查找鼠标所在的位置。

  2. Now, let’s review the code. The code handles pan and zoom in two ways: one is with mouse input and the other with touch input. You’ll see that it also has special conditions for when a touch device is being played remotely via the Unity Editor.

    A large portion of this code is vector math, and I will leave that for you to review on your own. The parts of this code I want to focus on are the parts relative to this chapter, specifically the parts involving Input and Touch. First, let’s look at the HandleMouse() method. I’ve highlighted the relevant parts:

    void HandleMouse() {
         if (Input.GetMouseButtonDown(0)) {
              lastPanPosition = Input.mousePosition;
         } else if (Input.GetMouseButton(0)) {
              PanCamera(Input.mousePosition);
         }
         float scroll = Input.GetAxis("Mouse ScrollWheel");
         ZoomCamera(scroll, zoomSpeedMouse);
    }

    Notice that it uses Input.GetMouseButtonDown(0) to see if the left mouse button is currently held down, Input.GetMouseButton(0) to see if the mouse button has been clicked, and Input.mousePosition to find where the mouse is located.

  3. 现在,让我们看看HandleTouch()方法中如何处理触摸输入。首先,它使用以下switch语句查看有多少根手指触摸屏幕
    开关(输入.触摸次数){

    Input.touchCount返回当前有多少根手指触摸屏幕。

  4. Now, let’s look at how input is handled with touch in the HandleTouch() method. First, it looks to see how many fingers are touching the screen with the following switch statement:
    switch(Input.touchCount) {

    Input.touchCount returns how many fingers are currently touching the screen.

  5. 当单根手指触摸屏幕时,相机可以平移。它首先必须获取手指的位置。它使用以下代码来实现此目的:
    触摸 touch = Input.GetTouch(0);
    如果 ( touch.phase == TouchPhase.Began ) {
         lastPanPosition = touch.position; 
         panFingerId = touch.fingerId; 
    } 否则,如果 ( touch.fingerId == panFingerId && touch.phase == TouchPhase.Moved ) {
         平移相机(触摸.位置);
    }

    我再次强调了相关代码。

  6. When a single finger is touching the screen, the camera can pan. It first must get the position of the finger. It does so with the following code:
    Touch touch = Input.GetTouch(0);
    if (touch.phase == TouchPhase.Began) {
         lastPanPosition = touch.position;
         panFingerId = touch.fingerId;
    } else if (touch.fingerId == panFingerId && touch.phase == TouchPhase.Moved) {
         PanCamera(touch.position);
    }

    Once again, I have highlighted the relevant code.

  7. 什么时候两根手指触摸屏幕,它将使用以下命令获取手指的位置:
    Vector2[] newPositions = new Vector2[]{ Input.GetTouch(0).position,  Input.GetTouch(1).position};

    它将第一根手指和第二根手指的位置存储在Vector2数组中。然后使用一些复杂的矢量数学来判断手指是相互靠近还是相互远离,从而产生捏合缩放效果。

  8. When two fingers are touching the screen, it will get the position of the fingers with the following:
    Vector2[] newPositions = new Vector2[]{Input.GetTouch(0).position, Input.GetTouch(1).position};

    It stores the position of the first finger and the second finger in a Vector2 array. It then uses some fancy vector math to see whether the fingers are getting closer to each other or further away from each other, creating a pinch-to-zoom effect.

  9. 我要重点关注的下一段代码是PanCamera()方法中的以下一行:
    Vector3 偏移量=相机.ScreenToViewportPoint (上次平移位置 - 新平移位置);

    这行代码至关重要,因为它将鼠标或手指的屏幕坐标转换为视口的坐标。

  10. The next piece of code that I want to focus on is the following line within the PanCamera() method:
    Vector3 offset = theCamera.ScreenToViewportPoint(lastPanPosition - newPanPosition);

    This line of code is crucial as it converts the screen coordinates of your mouse or finger to that of the viewport.

  11. 如果您尝试玩游戏,相机实际上不会平移!我们需要调用 DragCamera ()StopCameraDrag()方法,这样它就会知道何时获取输入。我们将使用Background Canvas上的 Event Triggers 来执行此操作。使用以下事件将Event Trigger组件添加到Background Canvas
  12. If you try playing the game, the camera won’t actually pan! We need to call the DragCamera() and StopCameraDrag() methods, so it will know when to get the inputs. We’ll do this with Event Triggers on the Background Canvas. Add an Event Trigger component to the Background Canvas with the following events:
图 8.46:背景画布上的事件触发器

图 8.46:背景画布上的事件触发器

Figure 8.46: The Event Trigger on the Background Canvas

  1. 现在检查完代码后,再次玩游戏以查看平移和缩放功能的实际效果。如果可以,我建议将移动设备插入计算机并通过 Unity Remote 运行游戏。您可以在此处查看有关如何使用 Unity Remote 的信息:https://docs.unity3d.com/Manual/UnityRemote5.xhtml
  2. Now that you’ve reviewed the code, play the game again to see the pan and zoom functions in action. If you can, I recommend also plugging a mobile device into your computer and running the game via Unity Remote. You can view information about how to use Unity Remote here: https://docs.unity3d.com/Manual/UnityRemote5.xhtml.
  3. 我们不希望游戏在暂停且库存面板打开时平移和缩放!因此,让我们更新ShowHidePanels.cs脚本以调用TurnOffPanAndZoom()TurnOnPanAndZoom()方法,它们切换canPancanZoom 布尔变量。

    将以下变量添加到ShowHidePanels.cs类:

    相机处理器 相机处理器;
  4. We don’t want the game to pan and zoom when it’s paused and the inventory Panel is up! So, let’s update the ShowHidePanels.cs script to call the TurnOffPanAndZoom() and TurnOnPanAndZoom() methods, which toggle the canPan and canZoom Boolean variables.

    Add the following variable to the ShowHidePanels.cs class:

    CameraHandler cameraHandler;
  5. 现在,我们需要初始化cameraHandler变量。使用以下代码添加Awake()方法
    void Awake() {¶ cameraHandler = GetComponent<CameraHandler>();¶}
  6. Now, we need to initialize the cameraHandler variable. Add an Awake() method with the following code:
    void Awake() {¶    cameraHandler = GetComponent<CameraHandler>();¶}
  7. 现在,将以下内容添加到TogglePanel()方法中。这将在游戏暂停或显示库存时调用TurnOffPanAndZoom()方法,并在两个面板均不可见时调用TurnOnPanAndZoom() :
    如果(inventoryUp || pauseUp)
    {
        相机处理程序.关闭平移和变焦();
    }
    别的
    {
        cameraHandler.TurnOnPanAndZoom();
    }
  8. Now, add the following to the TogglePanel() method. This will call the TurnOffPanAndZoom() method whenever the game is paused or showing the inventory and call TurnOnPanAndZoom() whenever neither of the Panels is visible:
    if (inventoryUp || pauseUp)
    {
        cameraHandler.TurnOffPanAndZoom();
    }
    else
    {
        cameraHandler.TurnOnPanAndZoom();
    }

你应该现在具有功能齐全的平移和缩放功能,当菜单可见时将被禁用!

You should now have a fully functional pan and zoom that are disabled when menus are visible!

这几乎是我在游戏 Barkeology 中使用的确切代码,您可以在 iOS 应用商店中找到:https ://apps.apple.com/kn/app/barkeology/id1500348850因此,如果您无法在移动设备上测试代码,但想查看其运行情况,您可以在那里查看它。

This is almost the exact code I used in my game Barkeology, which you can find on the iOS app store: https://apps.apple.com/kn/app/barkeology/id1500348850 So, if you do not have the ability to test your code on your mobile device, but would like to see it in action, you can view it there.

虽然这标志着本章的结束,但我们将在本文中继续研究事件系统,因此您将看到更多示例。

While this marks the end of the chapter, we will continue to work in the Event System throughout this text, so you will see plenty more examples.

概括

Summary

现在我们知道如何利用事件系统和为 UI 元素编程,我们可以开始制作交互式和可视化的 UI 元素。我们还可以创建当事件发生时其各种属性会发生变化的 UI。

Now that we know how to utilize the Event System and program for UI elements, we can start making interactive and visual UI elements. We can also create UI that has its various properties change when events occur.

我们在本章中讲了很多内容!我们讨论了如何访问 UI 元素的属性以及如何使用事件系统。我们还讨论了如何使用输入模块。现在,您可以创建响应用户输入的 UI 以及响应游戏内事件的 UI。

We covered a lot in this chapter! We discussed how to access the properties of UI elements and how to work with the Event System. We also discussed how to use the Input Module. Now, you can create UI that responds to user inputs as well as UI that responds to events within your game.

在下一章中,我们将了解 Unity 提供的另一个输入系统:新输入系统(是的,这是它的实际名称)。

In the next chapter, we will look at the other input system provided by Unity: the New Input System (yeah, that’s its actual name).

第 3 部分:可交互的 Unity UI 组件

Part 3: The Interactable Unity UI Components

在本部分中,您将探索 uGUI 系统提供的各个可交互组件。本部分将介绍如何布局和编程按钮。此外,还将介绍两种显示文本的方式:UI Text 和 Text-TextMeshPro。您将学习如何使用 UI 图像并为其添加各种效果。本部分将讨论如何使用遮罩、滚动条和滚动视图,以便您可以制作可扩展的 UI 菜单。最后,您将学习如何使用 Unity UI 系统提供的所有其他可交互 UI 组件。

In this part, you will explore the individual interactable components provided by the uGUI system. How to lay out and program for buttons is covered. Additionally, the two ways in which you can display text, UI Text and Text-TextMeshPro are covered. You will learn how to work with UI Images and add various effects to them. How to use masks, scrollbars, and scroll views is discussed so that you can make expandable UI menus. And lastly, you’ll learn how to use all the other Interactable UI Components provided by the Unity UI system.

本部分包含以下章节:

This part has the following chapters:

9

9

UI 按钮组件

The UI Button Component

Unity 的 UI 系统提供的按钮是图形对象,它们预先利用了我们在上一章中介绍的事件系统。当按钮被放置在场景中时,它会自动添加允许玩家与之交互的组件。这是有道理的,因为按钮的全部意义在于与它交互。让我们探索如何在游戏中添加和使用按钮。

Buttons provided by Unity’s UI system are graphical objects that preutilize the Event System we covered in the last chapter. When a Button is placed in a scene, it automatically has components added to it that allow the player to interact with it. This makes sense because the whole point of a Button is to interact with it. Let’s explore how to add and utilize buttons in our games.

在本章中,我们将讨论以下主题:

In this chapter, we will discuss the following topics:

  • 创建 UI 按钮并设置其属性
  • Creating UI Buttons and setting their properties
  • 如何设置按钮过渡效果,使按钮在突出显示、按下或禁用时改变外观
  • How to set button transitions that make the button change appearance when it is highlighted, pressed, or disabled
  • 如何使用隐形按钮区域来实现较大的点击区域
  • How to use invisible button zones to allow large tapping areas
  • 使用键盘或操纵杆在屏幕上导航按钮选择
  • Navigating button selection on screen with the keyboard or joystick
  • 如何创建看起来像是被物理按下的屏幕按钮
  • How to create an onscreen button that looks like it is physically being pressed
  • 按下按钮即可加载场景
  • Loading scenes with a button press
  • 创建按钮过渡动画
  • Creating Button Transition Animations

笔记

Note

示例部分之前的部分中显示的所有示例都可以在代码包中提供的 Unity 项目中找到。它们可以在标记为Chapter9 的场景中找到

All the examples shown in the sections before the Examples section can be found within the Unity project provided in the code bundle. They can be found within the scene labeled Chapter9.

每个示例图像都有一个标题,注明场景中的示例编号。

Each example image has a caption stating the example number within the scene.

在场景中,每个示例都在其自己的画布上,并且一些画布处于停用状态。要查看停用画布上的示例,只需在检查器中选中画布名称旁边的复选框即可。每个画布还具有自己的事件系统。如果您一次激活多个画布,这将导致错误

In the scene, each example is on its own Canvas, and some of the Canvases are deactivated. To view an example on a deactivated Canvas, simply select the checkbox next to the Canvas’ name in the Inspector. Each Canvas is also given its own Event System. This will cause errors if you have more than one Canvas activated at a time.

技术要求

Technical requirements

您可以在此处找到本章节的相关代码和资产文件https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2009

You can find the relevant codes and asset files of this chapter here: https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2009

用户界面按钮

UI Button

按钮是 UI 对象,需要玩家点击。您可以通过选择+ | UI | Button来创建按钮。当您创建按钮时,带有Text子对象的Button对象将被放置在场景中。与所有其他 UI 对象一样,如果创建按钮时场景中没有 Canvas 或 Event System 则会为您创建一个CanvasEvent System ,其中Canvas是按钮的父级

Buttons are UI objects that expect a click from the player. You can create a Button by selecting + | UI | Button. When you make a button, a Button object with a Text child will be placed in the scene. As with all other UI objects, if no Canvas or Event System is in the scene when you create the Button, a Canvas and Event System will be created for you, with the Canvas being a parent of your new Button:

图 9.1:向场景中添加新的 UI 按钮

图 9.1:向场景中添加新的 UI 按钮

Figure 9.1: Adding a new UI Button to the scene

如果您不想在按钮上显示文本,可以删除子文本对象

You can delete the child Text object if you do not want to have text displaying on your Button.

Button 对象有三个主要组件:Rect Transform(与所有其他 UI 图形对象一样)、Image组件和Button组件:

The Button object has three main components: Rect Transform (like all other UI graphical objects), an Image component, and a Button component:

图 9.2:新 UI 按钮的组件

图 9.2:新 UI 按钮的组件

Figure 9.2: The components of a new UI Button

我们将在下一章中更深入地讨论图像组件,但现在,只需知道图像组件确定按钮在标准状态下的外观。

We’ll discuss the Image component more thoroughly in the next chapter, but for now, just know that the Image component determines the look of the Button in its standard state.

按钮组件

The Button component

Button组件提供了所有允许玩家与按钮交互的属性,并确定当玩家尝试与按钮交互时按钮将执行的操作

The Button component provides all the properties that allow the player to interact with the button and determine what the Button will do when the player attempts to interact with it.

Button组件的第一个属性是Interactable属性。此属性决定按钮是否可以通过接受玩家的输入进行交互。默认情况下,此功能处于打开状态,但如果您想禁用按钮,可以将其关闭。

The first property of the Button component is the Interactable property. This property determines whether the Button can or cannot be interacted with by accepting input from the player. This is turned on by default but can be turned off if you want to disable the button.

您将看到按钮组件已附加了单击事件。当玩家在鼠标悬停在按钮上时单击并释放鼠标时,将触发单击事件。如果玩家单击按钮,将鼠标移到按钮的 Rect Transform 之外,然后释放鼠标,则按钮单击事件将不会注册。您可以按照第 8 章中设置事件的方式设置单击事件

You’ll see that the Button component already has an On Click Event attached to it. The On Click Event triggers when the player clicks and releases the mouse while hovering over the button. If the player clicks on the Button, moves the mouse outside of the Button’s Rect Transform, and then releases the mouse, the Button’s On Click Event will not register. You set the On Click Event the same way we set Events in Chapter 8.

过渡

Transitions

Button组件第二个属性是Transition属性。Transition属性决定按钮处于不同的状态。这些不同的状态不是突出显示(或正常)、突出显示按下选中禁用。这些转换是自动执行的,不需要编码。

The second property of the Button component is the Transition property. The Transition property determines the way the button will visually react when the button is in different states. These different states are not highlighted (or normal), highlighted, pressed, selected, or disabled. These transitions are performed automatically and do not require coding.

你可以指定四种不同类型的过渡:色调精灵交换动画n .

There are four different types of transitions you can assign: None, Color Tint, Sprite Swap, and Animation.

没有任何

None

选择“无”作为过渡类型将意味着按钮不会因不同的状态而发生视觉变化西。

Selecting None for the Transition type would mean that the button will not visually change for the different states.

色彩色调

Color Tint

颜色色调过渡类型将使按钮根据其状态改变颜色。您指定正常颜色突出显示颜色按下颜色选定颜色禁用颜色

The Color Tint transition type will make the button change color based on its state. You assign Normal Color, Highlighted Color, Pressed Color, Selected Color, and Disabled Color.

在下面的例子中,您可以看到当鼠标悬停在按钮上时,按钮变为绿色(因此突出显示),而当鼠标按下按钮时,按钮变为红色:

In the following example, you can see that the button changes to green when the mouse is hovering over it (hence, highlighting it) and turns red as the mouse is being pressed down on it:

图 9.3:第 9 章场景中的颜色交换示例

图 9.3:第 9 章场景中的颜色交换示例

Figure 9.3: Color Swap Example in the Chapter9 scene

如果您查看前面的示例,您会注意到单击按钮后按钮会变成黄色。这是因为它已被选中。要将其恢复为正常颜色,请单击按钮外部的任意区域。

If you view the preceding example, you’ll notice that the button turns yellow after it has been clicked. This is because it is selected. To return it to the normal color, click on any area outside of the button.

要使按钮进入赋予其“禁用颜色”的状态,必须禁用按钮的“可交互”属性。在以下屏幕截图中,您将看到在打开和关闭“可交互”属性时按钮如何变化:

For a Button to enter the state that will give it its Disabled Color, the Button’s Interactable property must be disabled. In the following screenshot, you will see how the button changes when the Interactable property is toggled on and off:

图 9.4:第 9 章场景中的禁用按钮示例

图 9.4:第 9 章场景中的禁用按钮示例

Figure 9.4: Disabled Button Example in the Chapter9 scene

您还可以选择目标图形。这是将接收过渡的图形。默认情况下,它被分配给按钮本身,因此按钮的图像在突出显示、按下或禁用时会改变颜色。但是,您可以选择将辅助图形设为目标图形。这意味着分配给目标图形的项目将改变颜色根据按钮的交互。在以下示例中,将辅助图像指定为目标图形。您会看到按钮没有经历过渡;相反,星形图像经历了过渡:

You also can select the Target Graphic. This is the graphic that will receive the transitions. By default, it is assigned to the Button itself, so the Button’s image will change color when it is highlighted, pressed, or disabled. However, you can choose to make a secondary graphic the Target Graphic. This means the item assigned to the Target Graphic will change color based on the interaction of the button. In the following example, a secondary image is assigned as the Target Graphic. You’ll see the button does not undergo transitions; instead, the star image undergoes transitions:

图 9.5:第 9 章场景中的目标图形示例

图 9.5:第 9 章场景中的目标图形示例

Figure 9.5: Target Graphic Example in the Chapter9 scene

需要注意的是,这些颜色将为目标图形的图像着色。因此,它本质上是在目标图形的顶部放置了一个颜色叠加层。如果目标图形的图像是黑色,这些色调似乎不会对图像产生任何影响。请注意,默认的正常颜色是白色。在图像上添加白色色调不会改变图像的颜色。

It’s important to note that these colors will tint the Target Graphic’s image. So, it essentially puts a color overlay on top of the Target Graphic. If the image of the Target Graphic is black, these tints will not appear to have any effect on the image. Note that the default Normal Color is white. Putting a white tint on an image does not change the color of the image.

颜色乘数属性允许使颜色更明亮或增加图形的 alpha 值。因此,如果图形的 alpha 值小于1,则此属性将增加图形的 alpha 值。这适用于所有(包括正常)状态。

The Color Multiplier property allows you to brighten up the colors or increase the alpha of the graphic. So, if the graphic has an alpha value that is less than 1, this property will increase the alpha of the graphic. This applies to all (including the normal) states.

淡入淡出持续时间属性是状态颜色之间淡入淡出所需的时间(以秒为单位)卢比

The Fade Duration property is the time (in seconds) it takes to fade between the state colors.

精灵交换

Sprite Swap

Sprite Swap过渡类型将使按钮针对不同的状态改变为不同的图像。

The Sprite Swap transition type will make the button change to different images for different states.

你会注意到没有属性为正常状态分配精灵。这是因为正常状态将只使用分配给图像组件的精灵

You’ll note that there is no property to assign a sprite for the normal state. This is because the normal state will just use the sprite assigned to the Image component.

我们在第 6 章中导入的精灵表有四个按钮图像,这将有助于演示精灵交换过渡:标记为uiElements_39uiElements_40uiElements_41uiElements_42的图像(如下面的屏幕截图所示):

The sprite sheet we imported in Chapter 6, has four button images that will be helpful in demonstrating the Sprite Swap Transition: the images labeled uiElements_39, uiElements_40, uiElements_41, and uiElements_42 (as shown in the following screenshot):

图 9.6:我们将用来演示按钮精灵交换的精灵

图 9.6:我们将用来演示按钮精灵交换的精灵

Figure 9.6: The sprites we will use to demonstrate button Sprite Swap

为了让按钮在适当的状态下显示这些图像,我们只需将uiElements_39分配给Image组件上的Source Image,将uiElements_40分配给Highlighted Sprite,将uiElements_41分配给Pressed Sprite,将uiElements_39分配给Selected Sprite,将uiElements_42分配给Disabled Sprite。我们还需要从按钮中删除子Text对象:

To have the button take on these images at the appropriate states, we simply need to assign uiElements_39 to the Source Image on the Image component, uiElements_40 to the Highlighted Sprite, uiElements_41 to the Pressed Sprite, uiElements_39 to the Selected Sprite, and uiElements_42 to the Disabled Sprite. We also need to delete the child Text object from the button:

图 9.7:第 9 章场景中的精灵交换示例

图 9.7:第 9 章场景中的精灵交换示例

Figure 9.7: Sprite Swap Example in the Chapter9 scene

请记住,您可以查看通过取消选择Interactable来禁用Sprite

Remember, you can view the Disabled Sprite by deselecting Interactable.

我一直觉得一个很棒的精灵交换动画很有吸引力,就是将一个按钮的图像应用到按下的图像上,这个按钮看起来是按下的。例如,我把左边的按钮稍微编辑了一下,就创建了右边的按钮。变化很小,但我通过稍微向下移动按钮的顶部来改变它,让它看起来是按下的:

A nice sprite swap animation that I always find appealing is applying an image of a button that appears down-pressed to the pressed image. For example, I took the button on the left and slightly edited it to create the button on the right. The change is slight, but I changed it by slightly moving down the top part of the button to make it look pressed:

图 9.8:缩进的按钮动画表

图 9.8:缩进的按钮动画表

Figure 9.8: The indented button animation sheet

并排查看时看起来没有太大区别。但是,当左侧图像用于源图像突出显示的 Sprite选定的 Sprite禁用的 Sprite,而右侧图像用于按下的 Sprite时,按钮会过渡到显示非常漂亮的按钮按下动画:

It doesn’t look like much of a difference when viewed side by side. However, when the left-hand image is used for Source Image, Highlighted Sprite, Selected Sprite, and Disabled Sprite, and the right-hand image is used for Pressed Sprite, the button transitions to show a very nice button-pressing animation:

图 9.9:第 9 章场景中的按下按钮示例

图 9.9:第 9 章场景中的按下按钮示例

Figure 9.9: Pressed Button Example in the Chapter9 scene

要查看实际效果,请查看Chapter9Scene中的“按下按钮示例画布”;它确实点击起来非常令人满意。

To see this in action, view Pressed Button Example Canvas in Chapter9Scene; it really is quite satisfying to click.

在第 11 章中,我们将探索如何在没有按钮过渡属性的情况下创建图像交换,例如静音/取消静音按钮n.

In Chapter 11, we will explore how to create an image swap without the button transition property, for something like a mute/unmute button.

动画片

Animation

动画转换允许按钮在其各种状态下进行动画处理

The Animation transition allows the button to animate in its various states.

动画过渡类型需要将动画组件附加到按钮上。它可以通过将一组预先存在的动画拖到按钮的检查器上来添加到按钮,或者您可以通过选择自动生成动画来创建一个全新的动画控制器。如果您使用预先存在的动画控制器,您可以简单地将动画分配给各个状态。但是,如果您生成新的动画控制器,则可以从动画窗口中的剪辑列表中选择状态,然后从该窗口中编辑它们。本章示例部分提供了制作具有动画过渡的按钮的示例呃。

Animation transition types require an Animator component attached to the Button. It can add a preexisting set of animations to a Button by dragging it onto the Button’s Inspector, or you can make a whole new Animator Controller by selecting Auto Generate Animation. If you use a preexisting Animator Controller, you can simply assign the Animations to the individual states. However, if you generate a new Animator Controller, you can select the state from the list of Clips in the Animation window and edit them from that window. An example of making a Button with animated transitions is provided in the Examples section of this chapter.

导航

Navigation

按钮具有导航属性,用于确定通过键盘或控制器输入突出显示的顺序

Buttons have a Navigation property that determines the order in which they will be highlighted via keyboard or controller inputs:

图 9.10:按钮组件上的导航属性

图 9.10:按钮组件上的导航属性

Figure 9.10: The Navigation property on the Button component

如果要导航到所有按钮,则场景中每个按钮都必须设置此属性。如果您回忆一下第 8 章,我们讨论了事件系统组件的First Selected属性。如果您为某个按钮分配了First Selected属性,则加载场景时该按钮将突出显示。如果您随后使用键盘浏览按钮,则导航将从具有First Selected属性的按钮开始。但是,如果您没有为某个按钮分配First Selected属性,则在使用鼠标选择了某个按钮之前,导航不会开始。下一个选择的按钮由您为按钮选择的导航选项决定。

Each Button within the scene must have this property set if you want to navigate to all the Buttons. If you recall from Chapter 8, we discussed the First Selected property of the Event System component. If you have a Button assigned to First Selected, that Button will be highlighted when you load the scene. If you then navigate through the Buttons with the keyboard, the navigation will begin at the Button with the First Selected property. However, if you do not have a Button assigned as First Selected, navigation will not start until a button has been selected with the mouse. The next button selected is determined by the navigation option you have selected for the buttons.

有五种导航选项水平垂直自动明确

There are five Navigation options: None, Horizontal, Vertical, Automatic, and Explicit.

选择“无”将禁用所有键盘导航到指定的按钮。请记住,这是针对单个按钮的,因此如果您想禁用所有键盘导航,则必须为所有按钮选择“无”

Selecting None will disable all keyboard navigation to the specified Button. Remember that this is for the individual button, so if you want to disable all keyboard navigation, you must select None for all Buttons.

水平垂直的解释非常直观。如果按钮的导航属性设置为水平,则在选择按钮时,将水平选择下一个选定的按钮,即使用左右箭头。垂直的工作原理类似;这表示该按钮导航,而不是导航到该按钮。因此,如果设置了水平的按钮具有导航属性,您仍然可以从另一个具有垂直按钮的按钮访问该按钮。

Horizontal and Vertical are pretty self-explanatory. If a Button has its Navigation property set to Horizontal, when it is selected, the next button selected will be chosen horizontally, meaning with the right and left arrows. Vertical works similarly; this represents the navigation away from that button, not to that button. So, if a button that has Horizontal set has its navigation property, you can still access that button from another with a vertical button.

自动将允许按钮水平和垂直导航,由其相对于其他按钮的位置自动确定。

Automatic will allow the Button to navigate both Horizontally and Vertically, as determined automatically by its position relative to the other buttons.

可视化按钮允许您查看导航设置的可视化表示。每个按钮将连接在一起,箭头表示将在其之后选择哪个按钮。每个箭头从按钮的侧面开始,以象征按下的方向箭头,并指向按下该箭头时将突出显示的下一个按钮。对于例如,如果箭头从按钮的右侧开始,则该箭头表示如果玩家按下键盘上的右键,下一个将被选择的按钮。以下示例显示了五个按钮的可视化,所有按钮的导航属性均设置“自动”

The Visualize button allows you to see a visual representation of the navigation setup. Each Button will be connected, with arrows demonstrating which Button will be selected after it. Each arrow begins on the side of the button to symbolize the directional arrow pressed, and it points at the next button that will be highlighted if that arrow is pressed. For example, if an arrow begins on a Button’s right, that arrow symbolizes what Button will be selected next if the player presses right on the keyboard. The following example shows the visualization of five Buttons, all with their Navigation property set to Automatic:

图 9.11:第 9 章场景中的导航示例

图 9.11:第 9 章场景中的导航示例

Figure 9.11: Navigation Example in the Chapter9 scene

在上面的示例中,每个按钮都有一个颜色色调过渡属性,其中突出显示颜色选定颜色均指定为绿色。标记为1 的按钮已在事件系统中指定为首次选定

In the preceding example, each Button has a Color Tint Transition property with the Highlighted Color and Selected Color assigned to green. The Button labeled 1 has been assigned as First Selected in the Event System:

图 9.12:按钮 1 被指定为第一个选定按钮的事件系统

图 9.12:按钮 1 被指定为第一个选定按钮的事件系统

Figure 9.12: Event System with Button 1 assigned to First Selected

因此,当场景开始播放时,它将自动突出显示。根据可视化图表,如果在场景加载后在键盘上选择右箭头键,则将选择标有2的按钮:

Therefore, when the scene begins playing, it will automatically be highlighted. Based on the visualized graph, if the right arrow key is selected on the keyboard after the scene loads, the Button labeled 2 will be selected:

图 9.13:第 9 章场景中的导航示例

图 9.13:第 9 章场景中的导航示例

Figure 9.13: Navigation Example in the Chapter9 scene

最后一种导航类型是显式导航,可以实现更好的控制。通过这种方式,您可以明确定义每次按下键盘时将访问哪个按钮。

The last Navigation type is Explicit and allows for significantly better control. With this, you can explicitly define which button will be accessed with each individual keyboard press.

图 9.14:显式导航类型的属性

图 9.14:显式导航类型的属性

Figure 9.14: The properties of the Explicit Navigation type

假设您希望玩家按 1-2-3-4-5 的顺序循环按钮,然后循环回到 1。您希望使用向上按钮或向右按钮来实现这一点。前面提到的任何导航方法都不允许这样做。但是,您可以使用显式导航类型来实现这一点。本章的第一个分步示例介绍了如何创建明确的按钮导航图

Let’s say that you wanted the player to cycle through the buttons in the order 1-2-3-4-5 and then loop back to 1. You want this to happen with either the up button or the right button. None of the previously mentioned navigation methods will allow that. However, you can achieve that with the Explicit Navigation type. The first step-by-step example of this chapter covers how to create an explicit button navigation map.

不可见的按钮区域

Invisible button zones

第 2 章中,我们讨论了屏幕上的点击区域。在手机游戏中,点击屏幕上的任意位置通常都会导致事件。例如,很多时候当您选择弹出窗口之外的区域时,它会关闭。其他示例是,您可以点击屏幕的左侧或右侧来前后移动角色。点击屏幕区域可能看起来不像按钮实现,但实际上就是这样!按钮只是不可见的。

In Chapter 2, we discussed tapping zones on the screen. Often, in mobile games, tapping anywhere on the screen will cause an event. For example, many times when you select outside of a pop-up window, it will close. Other examples are when you can tap on the left or right side of the screen to move a character back and forth. Tapping on areas of the screen may not seem like a button implementation, but it actually is! The buttons are just invisible.

让我们探索第一个场景。如果您查看第 9 章场景中的“关闭面板示例”游戏对象,您将看到一个面板,当按下信息按钮时会出现,当按下关闭按钮时会关闭,当按下面板外部的区域时也会关闭。这是通过在面板后面放置一个大的隐形按钮来实现的。为了使其正常工作,它需要位于信息按钮的前面(阻止射线投射到它)。

Let’s explore the first scenario. If you review the Close Panel Example GameObject of the Chapter9 scene, you will see a Panel that appears when an info button is pressed, closes when the close button is pressed, and also closes when the area outside of the Panel is pressed. This is accomplished by putting a large, invisible button behind the Panel. For it to work appropriately, it needs to be in front of the info button (blocking raycast to it).

图 9.15:第 9 章场景中的关闭面板示例

图 9.15:第 9 章场景中的关闭面板示例

Figure 9.15: Close Panel Example in the Chapter9 scene

即使背景区域会关闭面板,包含关闭按钮也是一个很好的设计选择。很多人并不直观地认为点击面板外部是关闭面板的操作,如果您不提供关闭按钮,用户将会花时间寻找它。

It’s a good design choice to include the close button, even if the background area will dismiss the Panel. Many people do not intuitively consider tapping outside a Panel to be an action that will close it and will spend time looking for the close button if you do not provide it.

现在让我们探讨第二种情况,即屏幕的两侧会引起不同的动作。在Tap Zone 示例 GameObject中,点击左侧会使拐杖糖向左移动,点击右侧会使拐杖糖向右移动。再次使用大型隐形按钮。

Now let’s explore the second scenario, where the two sides of the screen cause different actions. In the Tap Zone Example GameObject, tapping the left side moves the candy cane to the left, and tapping the right side moves the candy cane to the right. Once again, large invisible buttons are being utilized.

图 9.16:第 9 章场景中的点击区域示例

图 9.16:第 9 章场景中的点击区域示例

Figure 9.16: Tap Zone Example in the Chapter9 scene

当使用这些大型隐形按钮,考虑它们如何影响光线投射非常重要,即使它们是隐形的。它们会阻挡身后的东西!

When using these large invisible buttons, it is very important that you consider how raycast will be affected by them, even though they are invisible. They will block things behind them!

示例

Examples

对于本章的前三个示例,我们将暂时离开我们一直在处理的场景,构建一个新场景,以便我们试验按钮导航和场景加载。然后,我们将从第 8 章的场景中继续,为我们的场景添加一些按钮不。

For the first three examples in this chapter, we will momentarily step away from the scene we have been working on to build a new scene that will allow us to experiment with button navigation and scene loading. We’ll then pick up where we left off with our scene from Chapter 8, to add some buttons to our scene.

通过按钮导航并使用“第一个选定”

Navigating through Buttons and using First Selected

我们将构建一个模拟的开始屏幕,如下所示:

We’ll build out a faux start screen that appears as follows:

图 9.17:我们将要构建的开始屏幕场景

图 9.17:我们将要构建的开始屏幕场景

Figure 9.17: The start screen scene we will build

这些按钮大部分都是虚拟按钮,但我们将在下一个示例中设置播放按钮来加载我们一直在处理的场景

Most of these buttons will be dummy buttons, but we will set up the Play button in the next example to load the scene we have been working on.

让我们有能力进行实验按钮导航,我们将为其分配一个显式导航方案,以便我们可以按照以下模式循环浏览按钮:

To give us the ability to experiment with button navigation, we’ll assign an Explicit navigation scheme to it so that we can cycle through the buttons with the following pattern:

图 9.18:按钮导航图

图 9.18:按钮导航图

Figure 9.18: The button navigation map

首先,播放按钮将是选中。连续按下键盘上的向下键将导致以下选择路径:

First, the Play button will be selected. Pressing the down key on the keyboard continuously will result in the following selection path:

图 9.19:带有向下箭头的按钮导航流程

图 9.19:带有向下箭头的按钮导航流程

Figure 9.19: The button navigation flow with the down arrow

紧迫连续向上按钮将导致以下路径:

Pressing the up button continuously will result in the following path:

图 9.20:带有向上箭头的按钮导航流程

图 9.20:带有向上箭头的按钮导航流程

Figure 9.20: The button navigation flow with the up arrow

接下来,我们将学习如何布局屁股关注。

Next, we’ll learn how to lay out the buttons.

按钮布局

Laying out the Buttons

让我们先创建一个新场景并放置按钮

Let’s start by creating a new scene and laying out the buttons.

要制作如图 9.17所示的模拟开始屏幕,请完成以下步骤:

To make a faux start screen as shown in Figure 9.17, complete the following steps:

  1. 创建一个新的空场景并将其命名为Chapter9-Examples-StartScreen。打开新场景。
  2. Create a new empty scene and name it Chapter9-Examples-StartScreen. Open the new scene.
  3. 为了使这个场景具有与我们在前几章中制作的场景相同的背景,我们可以创建另一个Background Canvas,但从其他场景中复制它会更容易。我们将通过同时在 Hierarchy 中打开新场景和旧场景之一来实现这一点

    项目文件夹视图中,将Chapter8-Examples场景拖到Hierarchy中。你现在应该看到以下内容:

  4. To give this scene the same background as the scenes we’ve made in the last few chapters, we can create another Background Canvas, but it will be easier to just copy it from one of the other scenes. We’ll do this by having our new scene and one of our old scenes open in the Hierarchy at the same time.

    From the Project folder view, drag the Chapter8-Examples scene to the Hierarchy. You should now see the following:

图 9.21:同时加载两个场景

图 9.21:同时加载两个场景

Figure 9.21: Loading the two scenes simultaneously

  1. 选择背景画布Ctrl + D复制它。现在,将标记为Background Canvas (1)的副本拖到Chapter9-Examples-StartScreen场景中
  2. Select Background Canvas and press Ctrl + D to duplicate it. Now, drag the duplicate, labeled Background Canvas (1), to the Chapter9-Examples-StartScreen scene:
图 9.22:复制背景画布

图 9.22:复制背景画布

Figure 9.22: Duplicating Background Canvas

  1. 现在我们可以关闭Chapter8-Examples场景了,因为我们不再需要它了。为此,选择在第 8 章示例场景的右侧,选择Remove Scene。万一你不小心删除了某些内容,请在出现提示时选择Don't Save
    图 9.23:删除场景

    图 9.23:删除场景

    现在,层次结构中应该只有Chapter9-Examples-StartScreen,并且背景画布应该在场景中可见。

  2. We can now close the Chapter8-Examples scene, as we no longer need it. To do so, select the three dots on the right of the Chapter8-Examples scene, and select Remove Scene. Just in case you accidentally deleted something, select Don’t Save when prompted:

    Figure 9.23: Removing the Scene

    You should now have only Chapter9-Examples-StartScreen in the Hierarchy, and the Background Canvas should be visible in the scene.

  1. 将“背景画布(1)”重命名为“背景画布”并保存场景。
  2. Rename Background Canvas (1) to Background Canvas and save the scene.

笔记

Note

你会注意到,这样做后,场景中会有一个Canvas,但没有Event System。不过没关系;一旦我们添加新的 UI 元素,就会为我们添加一个 Event System 。

You’ll note that by doing this, we have a Canvas in the scene without an Event System. That’s okay, though; once we add new UI elements, an Event System will be added in for us.

  1. 在继续之前,我们应该注意控制台中弹出的警告消息。由于我们从另一个场景复制了背景画布,它正在尝试从另一个场景访问相机,但找不到它。

    系统会显示一条警告消息也会出现在Background CanvasCanvas组件上

    图 9.24:Canvas 组件错误消息

    图 9.24:Canvas 组件错误消息

    要解决这个问题,只需将主摄像机从当前场景拖到渲染摄像机槽,并将排序层 设置背景

    图 9.25:已分配主摄像头的 Canvas 组件

    图 9.25:已分配主摄像头的 Canvas 组件

  2. Before we can proceed, we should take care of a warning message that pops up in the Console. Since we copied Background Canvas from another scene, it is trying to access the camera from the other scene and can’t find it.

    A warning message will also appear on the Canvas component of the Background Canvas:

    Figure 9.24: The Canvas component error message

    To fix this, simply drag the Main Camera from the current scene to the Render Camera slot and set the Sorting Layer to Background.

    Figure 9.25: The Canvas component with the Main Camera assigned

  1. 现在,让我们添加“播放”按钮。创建一个新的 UI Canvas 来放置我们的按钮,并将其命名为Button Canvas。请注意,一旦您创建了Button Canvas ,就会为您创建一个事件系统游戏对象
  2. Now, let’s add the Play button. Create a new UI Canvas to place our Buttons on and name it Button Canvas. Note that once you create the Button Canvas, an Event System GameObject will be created for you.
  3. 创建新的 UIButton(+ | UI | Button )作为Button Canvas的子项,并赋予它以下Rect TransformImage 组件属性:
  4. Create a new UI Button (+ | UI | Button) as a child of Button Canvas and give it the following Rect Transform and Image component properties:
图 9.26:播放按钮上的属性

图 9.26:播放按钮上的属性

Figure 9.26: The properties on the Play Button

  1. 现在,选择播放按钮的子文本对象并设置其Rect TransformText组件属性,如下所示:
  2. Now, select the child Text object of the Play Button and set its Rect Transform and Text component properties as such:
图 9.27:播放按钮文本的属性

图 9.27:播放按钮文本的属性

Figure 9.27: The properties on the Play Button’s text

  1. 现在,让我们创建一个成就按钮、一个排行榜按钮和一个信息按钮,使它们显示如下:
    图 9.28:开始屏幕按钮的布局

    图 9.28:开始屏幕按钮的布局

    要实现上述布局,请使用以下属性并确保删除其子文本对象:

    图 9.29:三个按钮的属性

    图 9.29:三个按钮的属性

  2. Now, let’s create an Achievement Button, a Leaderboard Button, and an Info Button so they appear as follows:

    Figure 9.28: The layout of the start screen buttons

    To achieve the preceding layout, use the following properties and make sure to remove their child Text objects:

    Figure 9.29: The properties of the three buttons

  1. 现在,设置场景布局所剩无几的就是在场景的右下角创建Facebook 按钮Twitter 按钮。(忽略徽标已经过时的事实。)要实现9.26中的布局,请创建具有以下属性的两个按钮:
  2. Now all that is left to do to set up our scene’s layout is to create the Facebook Button and Twitter Button in the bottom-right corner of the scene. (Ignore the fact that the logos are extremely outdated.) To achieve the layout in Figure 9.26, create two buttons with the following properties:
图 9.30:两个按钮的属性

图 9.30:两个按钮的属性

Figure 9.30: The properties on the two buttons

你的场景应该正确现在布局好了,让我们开始设置导航在。

Your scene should be correctly laid out now, so let’s work on setting up the navigation.

设置显式导航和第一个选定项

Setting the explicit navigation and First Selected

如果您选择任意在您的 Button 的Button组件中,您应该看到以下导航路径:

If you select the Visualize button on any of your Button’s Button components, you should see the following navigation path:

图 9.31:自动按钮导航可视化

图 9.31:自动按钮导航可视化

Figure 9.31: The automatic button navigation visualization

这种导航设置比我在本文开头描述的设置允许的导航范围要大得多例如。这是因为每个按钮的导航默认设置为“自动” ,而“自动”允许多向导航。让我们让导航更线性一些,并将“播放按钮”设为事件系统中的第一个选定按钮

This navigation setup allows significantly more navigation range than the setup I described at the beginning of this example. This is because each button has its navigation set to Automatic by default, and Automatic allows multidirectional navigation. Let’s make our navigation a bit more linear and make our Play Button the First Selected Button in our Event System.

要设置本示例开头描述的导航,请完成以下步骤:

To set up the navigation described at the beginning of this example, complete the following steps:

  1. 在层次结构中选择事件系统,然后通过拖放将播放按钮分配到第一个选定插槽
    图 9.32:首先选择播放按钮的事件系统

    图 9.32:首先选择播放按钮的事件系统

    现在,当我们开始循环按钮时,我们的导航将从播放按钮开始。此外,如果我们在场景加载时按下Enter键,播放按钮将自动执行。

  2. Select the Event System in the Hierarchy and assign the Play Button to the First Selected slot by drag and drop:

    Figure 9.32: The Event System with the Play Button as First Selected

    Now, when we start cycling through our buttons, our navigation will begin at the Play Button. Also, if we were to hit the Enter key when this scene loads, the Play Button will automatically be executed.

  1. 现在,让我们设置所有的按钮具有显式导航类型。选择层次结构列表中的所有六个按钮
  2. Now, let’s set all the Buttons to have an Explicit Navigation type. Select all six of the buttons in the Hierarchy list.
  3. 全部选中后,从下拉菜单中将导航类型更改为显式。现在,每个按钮在其按钮组件中都应具有以下设置
  4. With all selected, change the Navigation type to Explicit from the dropdown menu. Now, each Button should have the following settings in its Button component.
  5. 为了更容易看到导航,让我们更改每个按钮上的“选定颜色”属性。在所有按钮仍处于选中状态的情况下,将“选定颜色”设置为深红色。虽然不太美观,但它可以让我们更容易看到按钮是否被选中。所有按钮上的按钮组件现在应如下所示:
    图 9.33:带有颜色色调过渡的按钮组件

    图 9.33:带有颜色色调过渡的按钮组件

    如果你玩游戏,你应该看到播放按钮变成红色,表示它已被选中(因为我们在步骤 1中将其设置为“首先选中” ):

    图 9.34:播放按钮被选中并显示为红色

    图 9.34:播放按钮被选中并显示为红色

  6. To make it easier to see our navigation, let’s change the Selected Colors property on each of our Buttons. With all the Buttons still selected, set Selected Color to dark red. It’s not very attractive, but it will make it easier for us to see whether our buttons are being selected. The Button component on all of your buttons should now look as follows:

    Figure 9.33: The Button component with Color Tint transitions

    If you play the game, you should see the Play Button colored red, symbolizing that it is selected (since we set it as First Selected in Step 1):

    Figure 9.34: Play Button selected and colored red

  1. 现在,我们可以明确地(因此得名)设置每个按钮要导航到的按钮,方法是将它们拖放到适当的插槽中。让我们首先为播放按钮设置导航,因为它将是第一个选择的按钮。

    根据图 9.189.20,按下向上键时,播放按钮应导航至Twitter 按钮,按下向下键时,播放按钮应导航至成就按钮。因此,从层次结构中将这些按钮分别拖放到标有“向上选择”“向下选择”的插槽中

    如果你有“可视化”按钮选择后,当您将按钮拖入其插槽时,您应该会看到导航可视化开始构建

  2. Now, we can explicitly (hence the name) set the Buttons that each individual Button is to navigate to by dragging and dropping them into the appropriate slots. Let’s set the Navigation for the Play Button first since it will be the first button selected.

    According to Figures 9.18 through 9.20, the Play Button should navigate to Twitter Button when the up key is pressed and Achievements Button when the down key is pressed. So, drag and drop those Buttons into the slots labeled Select On Up and Select On Down, respectively, from the Hierarchy.

    If you have the Visualize button selected, as you are dragging the Buttons into their slots, you should see the navigation visualization starting to build out.

图 9.35:播放按钮的导航属性

图 9.35:播放按钮的导航属性

Figure 9.35: The Navigation properties of the Play Button

笔记

Note

请记住,如果箭头从按钮顶部开始,则该箭头表示按下向上键时导航将到达的位置,如果箭头从按钮底部开始,则该箭头表示按下向下键时导航将到达的位置

Remember that if an arrow begins on top of a Button, that arrow symbolizes where navigation will go if the up key is pressed, and if the arrow begins on the bottom of a Button, it symbolizes where navigation will go if the down key is pressed.

  1. 玩游戏检查一下是否可行。按向上键,您会看到Twitter 按钮变成红色,表示已被选中。

    要看到成就按钮被选中,您实际上必须停止游戏并重新播放,因为我们没有设置Twitter 按钮返回播放按钮的导航。因此,停止游戏,再次按播放,然后按下向下键,您应该会看到成就按钮 突出显示为红色。

  2. Play the game to check and see whether it works. Press the up key and you should see the Twitter Button turn red, indicating it is selected.

    To see the Achievement Button become selected, you actually have to stop playing the game and replay, because we have not set up the navigation for the Twitter Button to go back to the Play Button. So, stop the game, press Play again, then press the down key, and you should see the Achievement Button highlight red.

笔记

Note

您无需重新启动游戏,也可以用鼠标突出显示“播放”按钮,然后导航到“成就”按钮。

Instead of restarting the game, you can also highlight the Play Button with your mouse so that you can then navigate to the Achievement Button.

  1. 一旦你设置了一个按钮的导航,其余的按钮虽然单调乏味,但并不太难。使用下表可帮助您设置其余按钮:
  2. Once you set up the navigation for one Button, the rest aren’t too difficult, albeit tedious. Use the following chart to help you set up the rest of the Buttons:

按钮

Button

选择向上

Select On Up

选择向下

Select On Down

播放按钮

Play Button

Twitter 按钮

Twitter Button

成就按钮

Achievement Button

成就按钮

Achievement Button

播放按钮

Play Button

排行榜按钮

Leaderboard Button

排行榜按钮

Leaderboard Button

成就按钮

Achievement Button

信息按钮

Info Button

信息按钮

Info Button

排行榜按钮

LeaderBoard Button

Facebook 按钮

Facebook Button

Facebook 按钮

Facebook Button

信息按钮

Info Button

Twitter 按钮

Twitter Button

Twitter 按钮

Twitter Button

Facebook 按钮

Facebook Button

播放按钮

Play Button

表 9.1:每个按钮的“向上选择”和“向下选择”分配

Table 9.1: The Select On Up and Select On Down assignments for each Button

完成后,您的导航可视化效果应如下所示

When you are done, your navigation visualization should look like the following:

图 9.36:最终的导航流程可视化

图 9.36:最终的导航流程可视化

Figure 9.36: The finalized Navigation flow visualization

如果您玩游戏,您应该能够使用箭头键轻松地循环按钮。

If you play your game, you should be able to easily cycle through the Buttons using the arrow keys.

我建议设置所有按钮具有水平垂直 导航模式,并查看它们与我们创建的不同之处,以便您可以看到,通过将预定义模式应用于所有按钮,此模式是无法实现的。

I recommend setting all the buttons to have Horizontal or Vertical Navigation patterns and seeing how they differ from what we have created so that you can see that this pattern is not attainable with a predefined pattern applied to all the Buttons.

按下按钮加载场景

Loading scenes with Button presses

现在我们已经布置好了开始屏幕现在,让我们连接播放按钮来玩游戏。首先,使用Ctrl + D复制您在第 8 章中创建的场景,名为Chapter8-Examples。新场景应称为Chapter9-Examples。我们的目标是让Chapter9-Examples-StartScreen中的播放按钮加载Chapter9-Examples场景。

Now that we have our start screen laid out, let’s hook up the Play Button to play our game. First, duplicate the scene you created in Chapter 8, called Chapter8-Examples, using Ctrl + D. The new scene should be called Chapter9-Examples. Our goal is to have the Play Button in Chapter9-Examples-StartScreen load the Chapter9-Examples scene.

为了制作Chapter9-Examples-StartScreen场景中的播放按钮,请加载Chapter9-Examples场景并完成以下步骤:

To make the Play Button in the Chapter9-Examples-StartScreen scene, load up the Chapter9-Examples scene and complete the following steps:

  1. 过渡若要在 Unity 中创建场景,首先必须确保它们均列在Build Settings中的Scenes In Build列表中。选择File | Build Settings(或Ctrl + Shift + B )。应显示以下内容:
  2. To transition between scenes within Unity, you must first ensure that they are each listed within the Scenes In Build list in the Build Settings. Select File | Build Settings (or Ctrl + Shift + B). The following should be visible:
图 9.37:构建设置中没有场景

图 9.37:构建设置中没有场景

Figure 9.37: The Build Settings with no scenes in the build

  1. 项目文件夹中,将Chapter9-Examples-StartScreenChapter9-Examples场景拖放到列表中:
    图 9.38:将场景添加到构建中

    图 9.38:将场景添加到构建中

    场景在此列表中出现的顺序无关紧要,除了第一个场景(列为场景0 的场景)。列表中的第一个场景应该是游戏加载时要加载的场景。因此,我们将Chapter9-Examples-StartScreen放在位置0是有意义的

  2. From the Project folder, drag and drop the Chapter9-Examples-StartScreen and Chapter9-Examples scenes into the list:

    Figure 9.38: Adding the scenes to the build

    The order in which scenes appear in this list does not matter, except for the first scene (the one listed as scene 0). The first scene in the list should be the scene you want to load when the game loads. Therefore, it makes sense for us to put Chapter9-Examples-StartScreen in position 0.

  1. 现在我们的场景Build Settings中,我们可以编写一个脚本来在它们之间导航。右键单击Project视图中的Scripts文件夹,然后选择Create | C# Script。将新脚本命名为LevelLoader。打开新的LevelLoader脚本并将代码替换为以下内容:
    使用 UnityEngine;
    使用 UnityEngine.SceneManagement;
     
    公共类 LevelLoader:MonoBehaviour {
     
        公共字符串sceneToLoad =“”;
     
        公共无效LoadTheLevel(){
            场景管理器.加载场景(场景到加载);
        }
    }

    上述代码包含一个函数 - LoadTheLevel()。此函数调用SceneManager类中的LoadScene方法。我们将加载sceneToLoad,这是我们将在Inspector中指定的字符串。请注意,UnityEngine.SceneManagement命名空间必须包含在脚本顶部的以下行中:

    使用 UnityEngine.SceneManagement;
  2. Now that our scenes are in the Build Settings, we can write a script that will allow us to navigate between them. Right-click on the Scripts folder in your Project view and select Create | C# Script. Name the new script LevelLoader. Open the new LevelLoader script and replace the code with the following:
    using UnityEngine;
    using UnityEngine.SceneManagement;
     
    public class LevelLoader : MonoBehaviour {
     
        public string sceneToLoad = "";
     
        public void LoadTheLevel() {
            SceneManager.LoadScene(sceneToLoad);
        }
    }

    The preceding code contains a single function—LoadTheLevel(). This function calls the LoadScene method in the SceneManager class. We will load the sceneToLoad, which is a string we will specify in the Inspector. Note that the UnityEngine.SceneManagement namespace must be included with the following line at the top of the script:

    using UnityEngine.SceneManagement;
  3. 如果你还没有打开Chapter9-Examples-StartScreen场景,请再次打开它。选择播放按钮并将LevelLoader脚本拖放到检查器中。
  4. If you still do not have the Chapter9-Examples-StartScreen scene open, open it again. Select the Play Button and drag and drop the LevelLoader script onto its Inspector.
  5. 现在,在要加载的场景中,输入Chapter9-Examples。你不需要把它放在引号中;因为sceneToLoad变量是一个字符串,所以Chapter9-Examples被认为是一个字符串,而你不需要把它放在引号中:
  6. Now, within the Scene To Load slot, type Chapter9-Examples. You don’t need to put it in quotes; since the sceneToLoad variable is a string, Chapter9-Examples is assumed to be a string without you needing to put it in quotes:
图 9.39: LevelLoader.cs 脚本组件

图 9.39: LevelLoader.cs 脚本组件

Figure 9.39: The LevelLoader.cs script component

  1. 现在剩下的就是连接按钮的点击事件。选择按钮组件的OnClick()事件列表底部的+号以添加新事件。我们要访问的脚本LevelLoader.cs位于播放按钮上,因此将播放按钮拖到对象槽中。现在,从函数下拉菜单中菜单,选择LevelLoader | LoadTheLevel
  2. Now all that is left is to hook up the Button’s click event. Select the + sign at the bottom of the OnClick() Event list of the Button component to add a new event. The script we want to access, LevelLoader.cs, is on the Play Button, so drag the Play Button into the object slot. Now, from the function dropdown menu, select LevelLoader | LoadTheLevel.
图 9.40:连接的 OnClick 事件

图 9.40:连接的 OnClick 事件

Figure 9.40: The OnClick event hooked up

就是这样!单击播放按钮或在突出显示时按Enter键时,您的播放按钮现在应该导航到Chapter9-Examples场景(就像在开头或使用键盘导航一样)。

That’s it! Your Play Button should now navigate to the Chapter9-Examples scene when clicked on or when you press Enter when it is highlighted (as it is at the beginning or with your keyboard navigation).

按钮动画过渡

Button Animation Transitions

按钮通常会以动画的形式吸引玩家的注意力。让我们为播放按钮添加动画过渡,这样当按钮处于正常状态时,它会跳动以吸引玩家的注意力

Often buttons are animated as a way to draw your attention to them. Let’s give the Play Button an Animation Transition so that when it is in its normal state it will pulsate to draw the attention of the player to it.

添加按钮动画在播放按钮上进行转换,完成以下步骤:

To add button Animation Transition on the Play Button, complete the following steps:

  1. 选择播放按钮并将其过渡类型更改动画
  2. Select the Play Button and change its Transition type to Animation.
  3. 我们没有预先构建的 Animator Controller,因此选择Auto Generate Animation来创建一个新的。
  4. We do not have an Animator Controller prebuilt, so select Auto Generate Animation to create a new one.
  5. 将会弹出一个窗口,要求您保存新创建的 Animator Controller。在Assets文件夹中创建一个名为Animations的新文件夹,并将新的 Animator Controller 作为Play Button保存到该文件夹​​中。

    您现在应该在新的动画文件夹中看到新的动画控制器

    图 9.41:项目中的播放按钮动画器

    图 9.41:项目中的播放按钮动画器

    您还将在播放按钮上看到新的Animator组件

    图 9.42:播放按钮动画组件

    图 9.42:播放按钮动画组件

  6. A window will pop up asking you to save the newly created Animator Controller. Create a new folder in the Assets folder, called Animations, and save the new Animator Controller as Play Button to the folder.

    You should now see the new Animator Controller in the new Animations folder:

    Figure 9.41: The Play Button animator in the Project

    You will also see the new Animator component on the Play Button:

    Figure 9.42: The Play Button Animator component

  1. 通过选择窗口|动画来打开动画窗口。如果要停靠此新窗口,请将其停靠以便当它启动时你仍然可以看到场景和游戏视图。
  2. Open the Animation window by selecting Window | Animation. If you want to dock this new window, dock it somewhere so that you can still see the Scene and Game views when it is up.
  3. 选择播放按钮并打开动画窗口。与过渡状态相关的各种动画剪辑将显示在动画剪辑下拉菜单中:
    图 9.43:播放按钮上的各种动画

    图 9.43:播放按钮上的各种动画

    我们要编辑正常过渡状态的动画,因此请确保选择它(默认选择的动画是正常)。

  4. Select the Play Button and open the Animation window. The various animation clips associated with the transition states will be visible in the animation clip dropdown menu:

    Figure 9.43: The various animations on the Play Button

    We want to edit the animation for the Normal transition state, so make sure it is selected (Normal is the animation selected by default).

  1. 为了使按钮看起来像是脉动的,我们需要影响它的缩放比例。选择添加属性|矩形变换,然后点击缩放旁边的+图标
    图 9.44:向播放按钮添加 Scale 属性

    图 9.44:向播放按钮添加 Scale 属性

    Scale属性现在应该出现在动画时间轴中:

    图 9.45:播放按钮刻度时间线

    图 9.45:播放按钮刻度时间线

  2. To make the button look like its pulsating, we want to affect its scale. Select Add Property | Rect Transform and then hit the + icon next to Scale:

    Figure 9.44: Adding the Scale property to the Play Button

    The Scale property should now be showing up in the Animation timeline:

    Figure 9.45: The Play Button Scale timeline

  1. 为了实现带缩放的脉动效果,我们希望按钮从正常大小开始,变大,然后恢复到正常大小。时间线上出现的菱形称为关键帧。按照我刚才描述的步骤操作,我们需要一个关键帧,就在时间轴的正中央。单击时间轴顶部(数字出现的位置)将时间轴移动到0:30标记。然后,选择添加关键帧按钮以添加新关键帧:
  2. To achieve the pulsation with scaling, we want the button to start at its normal size, get big, and then go back to its normal size. The diamonds that appear on the timeline are known as keyframes. To do what I just described, we need one more keyframe, right in the center of the timeline. Click on the top of the timeline (where the numbers appear) to move the timeline to the 0:30 mark. Then, select the Add keyframe button to add a new keyframe:
图 9.46:向时间轴添加额外的关键帧

图 9.46:向时间轴添加额外的关键帧

Figure 9.46: Adding an extra keyframe to the timeline

  1. 通过选择左侧的箭头展开“缩放”属性。您将看到,如果您选择任何关键帧,则所有三个缩放方向旁边都会出现数字1。这表示该帧的比例为 100%(或其正常比例):
  2. Expand the Scale property by selecting the arrow to its left. You will see that if you select any of the keyframes, the number 1 appears next to all three scaling directions. This indicates that the scale at that frame is 100% (or its normal scale):
图 9.47:展开播放按钮的缩放属性

图 9.47:展开播放按钮的缩放属性

Figure 9.47: Expanding the Scale property of the Play Button

  1. 选择中间的关键帧。单击数字1并输入新值,将Scale.xScale.y上的数字1更改为1.2。您可以按动画窗口中的“播放”按钮预览动画。您将看到按钮现在在场景中脉动。

    现在,当您玩游戏时,按钮应该会跳动(并且不会再变成红色)。请注意,它不会立即跳动,因为按钮只有在正常状态下才会跳动。由于我们已将其设置为First Selected,因此它在启动时被选中。要取消选择,只需单击场景中按钮以外的任意位置或使用键盘导航离开。一旦按钮不再被选中,它就会开始跳动。

  2. Select the keyframe in the center. Change the number 1 on Scale.x and Scale.y to 1.2 by clicking on the number 1 and typing the new value. You can press the Play button in the animation window to preview your animation. You will see that the button now pulsates in the scene.

    Your button should now pulsate when you play the game (and will not turn red any longer). Note that it does not pulsate immediately, because the button will only pulsate when it is in its normal state. Since we have it set to First Selected, it is selected on start. To remove the selection, simply click anywhere in your scene outside of the button or navigate away using your keyboard. Once the button is no longer selected, it should begin pulsating.

  3. 让我们重新使用红色选择,因为这样可以很容易地判断按钮何时被选中(即使它没吸引力)。从动画列表下拉菜单中,选择选定的动画。
  4. Let’s bring back the red selection since it made it easy to tell when the button was selected (even if it was unattractive). From the animation list dropdown menu, select the Selected animation.
图 9.48:选择播放按钮的选定动画

图 9.48:选择播放按钮的选定动画

Figure 9.48: Selecting the Selected animation for the Play Button

  1. 选择添加属性按​​钮,然后选择图像|颜色
  2. Select the Add Property button, then select Image | Color.
图 9.49:将颜色属性添加到播放按钮时间线

图 9.49:将颜色属性添加到播放按钮时间线

Figure 9.49: Adding the Color property to the Play Button timeline

  1. 选择第二个关键帧并按Delete键来删除它。
  2. Delete the second keyframe by selecting it and hitting the Delete key.
  3. 在剩余的关键帧上将rgba属性分别更改为1001
  4. Change the r, g, b, and a properties to 1, 0, 0, and 1, respectively, on the remaining keyframe.
图 9.50:调整播放按钮的颜色属性

图 9.50:调整播放按钮的颜色属性

Figure 9.50: Adjusting the Color property of the Play Button

现在当你玩游戏时,播放按钮在未被选中时应该脉动,被选中时则变为红色。

Now when you play the game, the Play Button should pulsate when it is not selected and change to red when it is.

这标志着有关按钮的示例的结束,但我们将在以后的章节中继续使用它们。

That marks the end of the examples concerning Buttons, but we will continue to use them in future chapters.

玛丽

Summary

一旦你学会了如何使用事件系统,使用按钮就很容易了。按钮是最常见的交互式 UI 元素,因此掌握它们对于有效的 UI 开发至关重要。不过,设置它们以便在点击时发挥作用只是整个过程的一半。如果你要为 PC、Mac或游戏机进行开发,你还需要确保正确设置按钮导航。

Once you learn how to work with the Event System, working with buttons is an easy extension. Buttons are the most common interactive UI element, so having a good grasp on them is essential to effective UI development. Setting them up so that they function when clicked on is only half the process, though. You want to also ensure that you have your button navigation set up properly if you will be developing for PC, Mac, or console.

我们还没有结束对按钮的学习!我们将在整本书中继续使用它们。一旦我们更彻底地探索了图像组件,我们将介绍更多有趣的按钮实现和转换。

We’re not done with Buttons! We’ll be working with them throughout this book. Once we explore the Image component more thoroughly, we will cover more interesting button implementations and transitions.

在下一章中,我们将介绍 UI Text 组件!

In the next chapter, we’ll cover the UI Text component!

10

10

UI Text 和 TextMeshPro

UI Text and TextMeshPro

我们已经花了一些时间研究 UI Text 对象,因为它们是最基本的图形 UI 元素之一。我们在第 6 章中简要讨论了它们,因为在没有任何视觉显示的情况下开始布局 UI 非常困难。Text 对象也始终是新按钮的子项,我们在上一章中讨论过。但是,我们还没有探索 Text 对象的属性或如何在代码中使用它们。

We’ve spent some time with UI Text objects already as they are the one of most basic graphical UI elements. We discussed them briefly in Chapter 6 because it was pretty hard to start laying out UI without having anything to display visually. Text objects are also always children of new Buttons, which we discussed in the previous chapter. However, we haven’t explored the properties of Text objects or how to work with them in code.

在本章中,我们将更深入地探索UI Text对象。我们还将讨论Text-TextMeshPro对象以及它们如何让我们更好地控制游戏中的文本。

In this chapter, we will explore UI Text objects more thoroughly. We will also discuss Text-TextMeshPro objects and how they allow for even more control of the text in our game.

在本章中,我们将讨论以下主题:

In this chapter, we will discuss the following topics:

  • 使用 UI Text 对象并设置其属性
  • Using UI Text objects and setting their properties
  • 使用 TextMeshPro -Text 对象并设置其属性
  • Using TextMeshPro -Text objects and setting their properties
  • 使用字体和字体资产
  • Working with fonts and font assets
  • 使用 UI Text 对象的标记格式和TextMeshPro 对象的样式表
  • Using markup format with UI Text objects and style sheets with TextMeshPro objects
  • 制作像输入一样的动画文本
  • Making Text that animates as if it’s being typed out
  • 开发一个可以轻松进行文本翻译的系统
  • Developing a system that allows for easy Text translation
  • 创建用于 UI Text 对象的自定义字体
  • Creating a custom font to be used with UI Text objects
  • 创建沿曲线环绕并以渐变方式呈现的文本
  • Creating Text that wraps along a curve and renders with a gradient

笔记

Note

本节展示的所有示例都可以在本书代码包中提供的 Unity 项目中找到。它们可以在第 10 章场景中找到。

All the examples shown in this section can be found within the Unity project provided in this book’s code bundle. They can be found within the Chapter10 scene.

每个示例图像都有一个标题,注明场景中的示例编号。

Each example image has a caption stating the example number within the scene.

在场景中,每个示例都在其自己的画布上,并且一些画布已被停用。要查看已停用的画布上的示例,只需在 Inspector 中选中画布名称旁边的复选框即可。每个画布还被赋予了自己的事件系统。如果您一次激活多个画布,这将导致错误。

In the scene, each example is on its own Canvas, and some of the Canvases have been deactivated. To view an example on a deactivated Canvas, simply select the checkbox next to the Canvas’ name in the Inspector. Each Canvas has also been given its own Event System. This will cause errors if you have more than one Canvas activated at a time.

技术要求

Technical requirements

您可以在此处找到本章节的相关代码和资产文件https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2010

You can find the relevant codes and asset files of this chapter here: https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2010

UI 文本游戏对象

UI Text GameObject

您可以创建一个新的 UI Text 对象使用+ | UI |文本

You can create a new UI Text object using + | UI | Text:

图 10.1:UI 文本游戏对象检查器

图 10.1:UI 文本游戏对象检查器

Figure 10.1: The UI Text GameObject Inspector

UI Text GameObject 包含Rect TransformCanvas Renderer组件,以及Text组件。

The UI Text GameObject contains the Rect Transform and Canvas Renderer components, as well as the Text component.

UI 文本组件为所连接的对象提供非交互式文本显示。此组件不允许您创建您可能感兴趣的所有类型的文本,但它允许大多数基本的文本显示。

The UI Text component gives the object it is attached to a non-interactive text display. This component does not allow you to create all types of text you may be interested in, but it does allow most basic text displays.

文本和字符属性

The Text and Character properties

Text属性改变文本将显示。在此框中输入的任何内容都将显示在文本对象中。

The Text property changes the text that will be displayed. Whatever is typed within this box will be displayed within the Text object.

Text属性下方是一组Character属性。这些属性允许您更改Text 属性字段内各个字符的属性。

Below the Text property is a group of Character properties. These properties allow you to change the properties of the individual characters within the Text property’s field.

Font属性决定了整个文本块使用哪种字体。默认情况下,Font设置为Arial 。要使用任何其他字体您必须将字体导入Asset文件夹。请参阅使用字体部分以了解如何引入其他字体。

The Font property determines which font is used for the entire block of text. By default, the Font is set to Arial. To use any other font, you must import the font into your Asset folder. Refer to the Working with fonts section to learn how to bring in additional fonts.

字体样式提供了随所提供字体提供的可用字体样式的下拉列表。可能的样式有普通粗体斜体粗体和斜体

Font Style provides a dropdown list of available font styles that come with the provided font. The possible styles are Normal, Bold, Italic, and Bold And Italic:

图 10.2:UI 文本组件的字体样式选项

图 10.2:UI 文本组件的字体样式选项

Figure 10.2: The UI Text component’s Font Style options

笔记

Note

值得注意的是,并非所有字体都支持所有列出的字体样式。

It’s important to note that not all fonts will support all the listed font styles.

字体大小决定文本的大小,而行距表示每行文本之间的垂直间距。

Font Size determines the size of the text, whereas Line Spacing represents the vertical spacing between each line of text.

如果选择了富文本属性,您可以包括标记标签文本属性字段中,它们将以富文本样式显示,而不是按键入的方式显示。如果未选择此属性,则文本将按键入的方式显示。有关使用富文本书写的更多信息,请参阅标记格式部分

If the Rich Text property is selected, you can include markup tags within the Text property field and they will appear with Rich Text styling rather than as typed. If this property is not selected, the text will display exactly as typed. Refer to the Markup format section for more information concerning writing with Rich Text.

段落属性

The Paragraph properties

下一组属性 —段落属性(图 10.1 — 允许您确定文本在 Rect Transform 边界内(或边界外)的显示方式。

The next set of properties – the Paragraph properties (Figure 10.1) – allow you to determine how the text will display within (or outside of) the Rect Transform’s bounds.

Alignment属性根据 Rect Transform 边界确定文本的对齐位置。您可以选择水平和垂直对齐选项。按钮表示相对于 Rect Transform 边界的位置,因此左水平对齐将使文本向上推到 Rect Transform 左边界的边缘

The Alignment property determines where the text will align based on the Rect Transform bounds. You can choose both horizontal and vertical alignment options. The buttons represent the position relative to the Rect Transform bounds, so the left horizontal alignment will have the text pushed up to the edge of the Rect Transform’s left bound:

图 10.3:UI 文本组件的对齐选项

图 10.3:UI 文本组件的对齐选项

Figure 10.3: The UI Text component’s Alignment options

按几何对齐属性可将文本对齐,就好像字形或字符被裁剪至不透明区域而不是其覆盖区域一样。此裁剪基于其字符映射。这可以提供更紧密的对齐,但也可能导致重叠

The Align by Geometry property aligns the text as if the glyphs or characters are cropped down to their opaque area rather than the area they cover. This cropping is based on their character map. This can give a tighter alignment but might also cause things to overlap.

水平溢出属性决定了文本对于矩形变换区域来说太宽时会发生什么。有两个选项:WrapOverflow。Wrap将导致文本继续在下一行,而Overflow导致文本扩展到矩形区域之外:

The Horizontal Overflow property determines what happens to text if it is too wide for the Rect Transform area. There are two options: Wrap and Overflow. Wrap will cause the text to continue on the next line, whereas Overflow will cause the text to expand past the rectangular area:

图 10.4:第 10 章场景中的水平溢出示例

图 10.4:第 10 章场景中的水平溢出示例

Figure 10.4: Horizontal Overflow Example in the Chapter10 scene

垂直溢出属性决定如果文本对于 Rect Transform 区域来说太长,会发生什么情况。有两个选项:TruncateOverflowTruncate将截断矩形区域外的所有文本,而Overflow将导致文本超出矩形区域。在下图中,两个文本框都有相同的文本,但Truncate会删除最后两行文本,因为它们超出了矩形区域,而Overflow 则允许它超出框:

The Vertical Overflow property determines what happens to text if it is too long for the Rect Transform area. There are two options: Truncate and Overflow. Truncate will cut off all text outside of the rectangular area, whereas Overflow will cause the text to expand past the rectangular area. In the following figure, both Text boxes have the same text, but Truncate removes the last two lines of text due to them being outside of the rectangular area, while Overflow allows it to go outside the box:

图 10.5:第 10 章场景中的垂直溢出示例

图 10.5:第 10 章场景中的垂直溢出示例

Figure 10.5: Vertical Overflow Example in the Chapter10 scene

Best Fit属性尝试调整文本大小,使其全部适合矩形区域。选择Best Fit属性时,将有两个新属性可用:Min SizeMax Size。这些属性允许您指定字体大小可以保持的范围。

The Best Fit property attempts to resize the text so that all of it fits within the rectangular area. When you select the Best Fit property, two new properties will become available: Min Size and Max Size. These properties allow you to specify the range the font size can maintain.

请记住,根据您所编写的文本,“水平溢出”属性可能会导致其工作方式与您预期的略有不同。

Keep in mind that depending on the text you have written, the Horizontal Overflow property may cause this to work slightly differently than you’d expect.

图 10.6:第 10 章场景中的最佳拟合示例

图 10.6:第 10 章场景中的最佳拟合示例

Figure 10.6: Best Fit Example in the Chapter10 scene

例如,两个文本图 10.6的框已选择“最佳适配”,但第一个框的“水平溢出”设置为“包裹” ,而第二个框的“水平溢出”设置“溢出”

For example, the two Text boxes in Figure 10.6 have Best Fit selected, but the first has Horizontal Overflow set to Wrap, while the second has it set to Overflow.

颜色和材质属性

The Color and Material properties

颜色材质属性允许您调整文本字体的外观。Color属性将设置文本的基本渲染颜色,这是更改字体颜色的最快方法。默认情况下,此属性设置为非常深(不是完全黑色)的灰色。Material属性允许您分配字体的材质。这让您可以更好地控制字体的外观,还允许您应用特定的着色器。默认情况下,此属性设置N

The Color and Material properties allow you to adjust the appearance of the Text’s font. The Color property will set the base rendering color of the Text and is the quickest way to change the font’s color. By default, this property is set to a very dark (not fully black) gray. The Material property allows you to assign a material to your font. This gives you more control over the look of the font and also allows you to apply specific shaders. By default, this property is set to None.

Raycast 和 Maskable 属性

The Raycast and Maskable properties

Raycast Target属性决定对象的 Rect Transform 区域是否将阻挡光线投射。如果选中此属性,则点击不会在其后面的 UI 对象上注册。如果未选中,则可以点击对象后面的项目。如果您希望文本阻挡光线投射,但不阻挡其整个区域,您可以使用各种Raycast Padding属性来调整区域

The Raycast Target property determines whether the object’s Rect Transform area will block raycasts or not. If this property is selected, clicks will not register on UI objects behind it. If it is not selected, items behind the object can be clicked. If you’d like for the Text to block raycasts, but not over its entire area, you can adjust the area with the various Raycast Padding properties.

最后一个属性Maskable决定了文本是否可以被屏蔽。我们将在第 12 章中讨论这个问题。

The last property, Maskable, determines if the Text can be masked. We will discuss this in Chapter 12.

文字-TextMeshPro

Text-TextMeshPro

有一点限制了解 UI Text 可以做什么。如果您发现自己想要对文本进行一些无法通过 UI Text 完成的格式化,那么您可以使用 TextMeshPro GameObject 来完成。例如,如果您想使用带下划线的文本,我建议使用 TextMeshPro GameObject。TextMeshPro 资源允许进行更多的文本控制。此外,与标准 UI Text 相比,它的渲染允许文本在更高的分辨率和点大小下清晰显示

There is a bit of a limitation to what you can do with UI Text. If you find yourself wanting to accomplish some formatting with your text that you are unable to do with UI Text, you may be able to accomplish it with a TextMeshPro GameObject. For example, if you want to use underlined text, I recommend using a TextMeshPro GameObject. TextMeshPro assets allow for significantly more text control. Additionally, its rendering allows text to appear crisp at more resolutions and point sizes than what’s possible with the standard UI Text.

TextMeshPro 曾经是 Unity Asset Store 中的付费资产,但 Unity 于 2017 年 3 月左右采用了它,现在免费提供。但是,要使用 TextMeshPro 资产,您必须下载必要的资源。要下载 TextMeshPro 资源,请尝试通过转到+ | UI | Text - TextMeshPro将 TextMeshPro - Text 添加到您的场景中;您将看到以下弹出窗口:

TextMeshPro used to be a paid asset in the Unity Asset Store, but it was adopted by Unity around March 2017 and is now available for free. However, to use TextMeshPro assets, you have to download the necessary resources. To download the TextMeshPro resources, attempt to add a TextMeshPro - Text to your scene by going to + | UI | Text - TextMeshPro; you will see the following popup:

图 10.7:TMP 导入器

图 10.7:TMP 导入器

Figure 10.7: The TMP Importer

选择导入 TMP Essentials以获取所有必要的资产。我还建议选择导入 TMP 示例和附加内容

Select Import TMP Essentials to get all the necessary assets. I also recommend selecting Import TMP Examples & Extras.

笔记

Note

从 UI 菜单中选择任何 TextMeshPro 游戏对象(文本 - TextMeshPro、按钮 - TextMeshPro、下拉菜单 - TextMeshPro 或输入字段 - TextMeshPro)都会弹出前面屏幕截图中的弹出窗口,并允许您下载必要的资产。

Selecting any of the TextMeshPro GameObjects from the UI menu (Text - TextMeshPro, Button - TextMeshPro, Dropdown - TextMeshPro, or Input Field - TextMeshPro) will bring up the popup from the preceding screenshot and allow you to download the necessary assets.

一旦下载后,您无需再次下载。

Once you’ve downloaded it, you will not have to do so again.

由于 TextMeshPro 的稳健性,很遗憾我无法在本章中介绍您可以使用它做的所有事情。相反,我将对其功能进行广泛的概述。幸运的是,TextMeshPro 资产随附有许多示例和良好的文档,可以在这里找到:

Due to the robustness of TextMeshPro, I sadly can’t cover everything you can do with it within this chapter. Instead, I will provide a broad overview of its functionality. Luckily, the TextMeshPro asset comes with many examples and good documentation, which can be found here:

https://docs.unity3d.com/Packages/com.unity.textmeshpro@4.0/manual/index.xhtml

https://docs.unity3d.com/Packages/com.unity.textmeshpro@4.0/manual/index.xhtml

当您创建一个新的TextMeshPro - Text GameObject 时,您将看到一个具有以下组件的 GameObject:

When you create a new TextMeshPro - Text GameObject, you will see a GameObject with the following component:

图 10.8:TextMeshPro - 文本组件

图 10.8:TextMeshPro - 文本组件

Figure 10.8: The TextMeshPro - Text component

您还可以创建 Text-TextMeshPro 游戏对象通过GameObject | 3D Object | Text-TextMeshPro在 Unity 的 UI 系统之外。这将独立于 UI 渲染文本,并且无需Canvas。

You can also create Text-TextMeshPro GameObjects outside of Unity’s UI system via GameObject | 3D Object | Text-TextMeshPro. This will render the text independent of the UI and without a Canvas.

GameObject 本身将被命名为Text (TMP),并且为了简单起见,我将这样引用它。

The GameObject itself will be named Text (TMP) and, for simplicity’s sake, is how I will reference it.

与所有其他 UI 对象一样,Rect TransformCanvas Renderer组件也附加到 GameObject。Text (TMP) GameObject的图形显示由TextMeshPro - Text ( UI)组件控制

As with all other UI objects, a Rect Transform and a Canvas Renderer component are attached to the GameObject as well. The graphic display of the Text (TMP) GameObject is controlled by the TextMeshPro - Text (UI) component.

让我们研究一下TextMeshPro – 文本(UI) c的属性组件。

Let’s investigate the properties of the TextMeshPro – Text (UI) component.

文本输入属性

Text Input properties

您可以输入文本希望在文本输入部分显示:

You can enter the text you wish to display within the Text Input section:

图 10.9:TextMeshPro - Text 组件的文本输入设置

图 10.9:TextMeshPro - Text 组件的文本输入设置

Figure 10.9: The TextMeshPro - Text component’s Text Input settings

启用 RTL 编辑器属性允许您创建从右到左显示的文本,这对于某些语言来说是必需的。当您选择它时,文本将以从右到左的顺序出现在第二个区域中:

The Enable RTL Editor property allows you to create text that will display from right to left which is necessary for some languages. When you select it, the text will appear in a second area in its right-to-left order:

图 10.10:TextMeshPro - Text 组件的 RTL Text Input 设置

图 10.10:TextMeshPro - Text 组件的 RTL Text Input 设置

Figure 10.10: The TextMeshPro - Text component’s RTL Text Input setting

文本样式设置可让您指定文本样式。您将从下拉列表中看到多个预定义选项:

The Text Style setting lets you specify a style for the text. You’ll see multiple pre-defined options from the dropdown:

图 10.11:TextMeshPro - Text 组件的文本样式设置

图 10.11:TextMeshPro - Text 组件的文本样式设置

Figure 10.11: The TextMeshPro - Text component’s Text Style settings

我们将看看如何使用更详细地参阅章。

We’ll look at how to use this more thoroughly in the Style sheets section of this chapter.

主要设置

Main Settings

主要设置部分允许您调整文本的所有属性:

The Main Settings section allows you to adjust all the properties of the text:

图 10.12:TextMeshPro - Text 组件的主要设置部分

图 10.12:TextMeshPro - Text 组件的主要设置部分

Figure 10.12: The TextMeshPro - Text component’s Main Settings section

字体资源属性代表将使用的字体。默认情况下,字体资产设置为LiberationSans SDF (TMP_Font Asset) 。我想指出的是,字体和字体资产是两个不同的东西。字体用于 UI Text GameObjects,而字体资产用于 TextMeshPro GameObjects。我将在使用字体部分讨论这些差异,以及如何导入新字体和创建新的字体资产

The Font Asset property represents the font that will be used. By default, Font Asset is set to LiberationSans SDF (TMP_Font Asset). I want to point out that fonts and font assets are two different things. Fonts are used in UI Text GameObjects, whereas font assets are used in TextMeshPro GameObjects. I’ll discuss these differences, as well as how to import new fonts and create new font assets, in the Working with fonts section.

Font Asset属性需要材质来渲染。任何包含字体资产名称的材质都会出现在Material Preset列表中。您可以创建自己的材质,但当您创建新的字体资产时,它会附带一个默认材质。无论在此处选择哪种材质,它都会出现在组件底部的Extra Settings下方。从此区域,您还可以选择材质的着色器:

The Font Asset property needs a material to render. Any material that contains the name of the font asset will appear in the Material Preset list. You can create your own material, but when you create a new font asset, it comes with a default material. Whichever material is selected here will also appear at the bottom of the component, below the Extra Settings. From this area, you can also select the material’s shader:

图 10.13:TextMeshPro - 文本的材质属性

图 10.13:TextMeshPro - 文本的材质属性

Figure 10.13: The TextMeshPro - Text’s material properties

字体样式属性允许您为文本创建基本格式。您可以从粗体斜体下划线删除线小写大写小型大写中进行选择。您可以选择前四个设置的任意组合;但是,您只能在小写大写小型大写之间进行选择

The Font Style property allows you to create basic formatting for your text. You can select from Bold, Italic, Underline, Strikethrough, Lowercased, Uppercased, or Small Caps. You can choose any combination of the first four settings; however, you can only choose between Lowercase, Uppercase, or Small Caps.

Font Size属性的工作方式与你预期的一样,但你也可以选择Auto SizeAuto Size属性将根据你指定的属性,尝试将文本尽可能地适合 Rect Transform 的边界框:

The Font Size property works as you would expect, but you also have the option to select Auto Size. The Auto Size property will attempt to fit the text within the bounding box of the Rect Transform as best as it can based on the properties you specify:

图 10.14:TextMeshPro - Text 组件的字体大小属性

图 10.14:TextMeshPro - Text 组件的字体大小属性

Figure 10.14: The TextMeshPro - Text component’s Font Size properties

您可以指定最小 ( Min ) 和最大 ( Max ) 字体大小以及WD%Line属性。WD %属性允许您水平挤压文本以使字符更高,而Line属性允许您指定行高。

You can specify the minimum (Min) and maximum (Max) font size along with the WD% and Line properties. The WD% property allows you to squeeze text horizontally to make the characters taller, whereas the Line property allows you to specify line height.

您可以使用“顶点颜色”属性或“颜色渐变”属性来更改文本的颜色

You can change the color of the text using either the Vertex Color property or the Color Gradient property:

图 10.15:TextMeshPro - Text 组件的颜色属性

图 10.15:TextMeshPro - Text 组件的颜色属性

Figure 10.15: The TextMeshPro - Text component’s color properties

本章末尾的示例部分中有一个使用颜色渐变属性的示例。如果您希望颜色设置覆盖任何<color>标记标签,则可以选择“覆盖标签”

An example of using the Color Gradient property can be found at the end of this chapter in the Examples section. You can select Override Tags if you want the color settings to override any <color> markup tags.

您可以在间距选项区域设置字符单词段落之间的间距

You can set the spacing between Characters, Words, Lines, and Paragraphs in the Spacing Options area.

与标准 UI Text 相比,TextMeshPro-Text 中还提供了更多的对齐选项

You also have significantly more Alignment options available to you in TextMeshPro-Text than you do with the standard UI Text.

就像 UI Text 一样,你可以启用或禁用Wrapping。但是,你有更多的Overflow选项,如以下屏幕截图所示:

Just as with UI Text, you can enable or disable Wrapping. You have significantly more Overflow options, however, as shown in the following screenshot:

图 10.16:TextMeshPro - Text 组件的溢出选项

图 10.16:TextMeshPro - Text 组件的溢出选项

Figure 10.16: The TextMeshPro - Text component’s Overflow options

OverflowTruncate 的工作方式与 UI Text 对象上的类似。在其他选项中,我这次只想提一下Ellipsis选项。它将文本截断到文本框区域,但会添加省略号 (…):

Overflow and Truncate work similarly to those on the UI Text objects. Of the other options, the only one I want to mention at this time is the Ellipsis option. It will truncate the text to the text box area but add an ellipsis (…):

图 10.17:第 10 章场景中的 Text Mesh Pro Overflow 示例

图 10.17:第 10 章场景中的 Text Mesh Pro Overflow 示例

Figure 10.17: Text Mesh Pro Overflow Example in the Chapter10 scene

水平映射和垂直映射属性允许您影响纹理在文本中的显示方式:

The Horizontal Mapping and Vertical Mapping properties allow you to affect the way a texture is displayed across the text:

图 10.18:TextMeshPro - Text 组件的水平映射选项

图 10.18:TextMeshPro - Text 组件的水平映射选项

Figure 10.18: The TextMeshPro - Text component’s Horizontal Mapping options

你所做的大部分工作是为了定制你的字体将在“文本输入”部分和“主要设置”部分中完成,但让我们看看你将根据自己的需要调整的一些更细微的设置。字型。

Most of the work you do to customize your font will be done in the Text Input section and the Main Settings section, but let’s look at some more nuanced settings that you will adjust with your fonts.

额外设置

Extra Settings

必须扩展额外设置菜单可见。它允许您调整字体的一些不太常见的设置:

The Extra Settings menu has to be expanded to be visible. It allows you to adjust some less-common settings of the font:

图 10.19:TextMeshPro - 文本额外设置部分

图 10.19:TextMeshPro - 文本额外设置部分

Figure 10.19: The TextMeshPro - Text Extra Settings section

此菜单中最值得注意的属性是添加Margins的能力和启用Raycast Target的能力

The most notable properties in this menu are the ability to add Margins and the ability to enable Raycast Target.

最后,您可以指定是否是否要启用字距调整或允许额外填充。选择字距调整将使用字体文件提供的字距调整数据。选择额外填充将在其精灵图集上的精灵字形周围添加一些填充

Lastly, you can specify whether or not you want to Enable Kerning or allow Extra Padding. Selecting Kerning will use the kerning data provided by the font file. Selecting Extra Padding will add a little padding around the glyphs of the sprite on its sprite atlas.

笔记

Note

您可以了解我在这里忽略或跳过的任何属性https://docs.unity3d.com/Packages/com.unity.textmeshpro@4.0/manual/TMPObjectUIText.xhtml

You can learn about any of the properties I glossed over or skipped here: https://docs.unity3d.com/Packages/com.unity.textmeshpro@4.0/manual/TMPObjectUIText.xhtml.

TextMeshPro 项目设置

TextMeshPro Project Settings

除了能够要通过其组件调整场景中每个 TextMeshPro 对象的单独设置,您还可以通过项目设置编辑>项目设置)调整 TextMeshPro 项目范围的设置:

In addition to being able to adjust the individual settings of each TextMeshPro object you have within your scene via their components, you can also adjust TextMeshPro project-wide settings via the Project Settings (Edit > Project Settings):

图 10.20:TextMeshPro – 项目设置

图 10.20:TextMeshPro – 项目设置

Figure 10.20: The TextMeshPro – Project Settings

这些可以让你设置各种默认为新创建了 TextMeshPro 对象。您可以https://docs.unity3d.com/Packages/com.unity.textmeshpro@4.0/manual/Settings.xhtml了解有关每个属性的更多信息。

These let you set the various defaults for newly created TextMeshPro objects. You can learn more about each property at https://docs.unity3d.com/Packages/com.unity.textmeshpro@4.0/manual/Settings.xhtml.

使用字体

Working with fonts

极有可能您不会想使用默认的Arial(UI Text)Liberation Sans(TMP Text)字体,而想将自定义字体引入您的项目。让我们探索如何找到这些文本资源并在您的项目中使用它们。

It’s extremely likely that you won’t want to use the default Arial (UI Text) or Liberation Sans (TMP Text) fonts and will want to bring a custom font into your project. Let’s explore how you can both find these text resources and use them in your project.

笔记

Note

您不必在计算机上安装字体(在 Unity 之外的程序中使用)即可在 Unity 中使用该字体。

You do not have to install a font onto your computer (to use within programs outside of Unity) to use the font within Unity.

导入新字体

Importing new fonts

字体文件格式Unity 接受的格式是.tff (TrueType) 和.otf (OpenType)。您可以在多个地方获取这些文件。我的最喜欢的地方查找字体如下

The font file formats accepted by Unity are .tff (TrueType) and .otf (OpenType). You can get these files in multiple places. My favorite places to find fonts are as follows:

Google Fonts上的所有字体都是开源的,个人或商业用途均可免费使用(至少在撰写本书时),但Font SquirrelDaFont上的字体有不同的许可选项。在使用任何字体之前,请确保它都有符合您需求的许可协议

All the fonts on Google Fonts are open source and are free for personal or commercial use (at least at the time of writing this book), but the fonts on Font Squirrel and DaFont have varying licensing options. Ensure that any font you get has a licensing agreement that meets your needs before you use it.

下载完所选字体后,只需将字体拖到项目的Assets文件夹中即可。我强烈建议您在Assets文件夹中创建一个名为Fonts的文件夹,将所有字体文件都放在其中。

Once you’ve downloaded the font of your choice, simply drag the font into your project’s Assets folder. I highly recommend that you create a folder called Fonts within your Assets folder in which you place all of your font files.

然后,您可以选择在Inspector中调整字体的导入属性。以下屏幕截图显示了从 Google Fonts 下载的BungeeShade-Regular字体的导入设置

Then, you can adjust the font’s import properties in the Inspector if you so choose. The following screenshot shows the import settings of the BungeeShade-Regular font, which has been downloaded from Google Fonts:

图 10.21:Bungee Shade-Regular 字体检查器

图 10.21:Bungee Shade-Regular 字体检查器

Figure 10.21: The Inspector of the Bungee Shade-Regular font

在这里,你可以调整有关如何处理字体的一些设置引擎。

Here, you can adjust quite a few of the settings concerning how the font will be handled by the engine.

字体大小

Font Size

字体大小设置决定字体在 Unity 创建的纹理图集中显示的大小。增加或减少“字体大小”设置将改变纹理图集中各种字形的大小。如果您的字体在游戏中显得模糊,调整“字体大小”设置可能会改善 它的外观。

The Font Size setting determines how large the font will appear on its Unity-created texture atlas. Increasing or decreasing the Font Size setting will change the size of the various glyphs on the texture atlas. If your font appears fuzzy in your game, adjusting the Font Size setting may improve its appearance.

渲染模式

Rendering Mode

渲染模式设置告诉 Unity 如何字形将被平滑处理。可能的选项包括SmoothHinted SmoothRasterOS Default

The Rendering Mode settings tell Unity how the glyphs will be smoothed. The possible options are Smooth, Hinted Smooth, Raster, and OS Default.

平滑渲染模式是最快的渲染模式。它使用抗锯齿渲染,这意味着它将平滑锯齿状、像素化的边缘。提示平滑渲染模式也会平滑边缘,但它将使用字体数据文件中包含的“提示”来确定如何填充这些锯齿状边缘。这种渲染模式比平滑渲染模式慢,但看起来可能比平滑更清晰,更容易阅读。提示光栅渲染模式不提供抗锯齿,而是提供锯齿或锯齿状边缘。这是最清晰、最快的渲染模式。操作系统默认将默认为 Windows 或 Mac OS 上操作系统的首选项设置。这将从平滑中选择 暗示平滑

The Smooth rendering mode is the fastest rendering mode. It uses anti-aliased rendering, which means it will smooth out jagged, pixelated edges. The Hinted Smooth rendering mode will also smooth out edges, but it will use the “hints” contained within the font’s data files to determine how to fill in those jagged edges. This is a slower rendering mode than Smooth but will likely look crisper and be easier to read than Smooth. The Hinted Raster rendering mode does not provide anti-aliasing and instead provides aliased or jagged edges. This is the crispest and the quickest of the rendering modes. OS Default will default to whatever the operating system’s preferences are set to on Windows or Mac OS. This will select from Smooth or Hinted Smooth.

特点

Character

Character属性决定字体的哪种字符集将被导入到字体纹理图集中。有六个选项:动态UnicodeASCII 默认集ASCII 大写ASCII 小写自定义集

The Character property determines which character set of the font will be imported into the font texture atlas. There are six options: Dynamic, Unicode, ASCII default set, ASCII upper case, ASCII lower case, and Custom set:

将Character属性设置为Dynamic(默认)将仅包含所需的角色。这会减少字体所需的纹理大小,从而减少游戏的下载大小。

Setting the Character property to Dynamic (which is the default) will only include the characters that are needed. This reduces the texture size needed for the font and, in turn, the download size of the game.

Unicode用于包含 ASCII 字符集不支持的字符的语言。因此,例如,如果您想显示日语文本,则需要使用Unicode

Unicode is used for languages that have characters that are not supported in an ASCII character set. So, for example, if you want to display text in Japanese, you will want to use Unicode.

如果您想在脚本中包含Unicode字符,则需要使用 UTF-16 编码保存它们。这样您就可以在代码中直接将Unicode字符作为字符串输入,以便它们可以在屏幕上的文本对象中显示。

If you’d like to include Unicode characters in your scripts, you need to save them with UTF-16 encoding. This will allow you to type Unicode characters directly in your code as strings so that they can display in your Text objects on screen.

Google Fonts提供的Noto 字体支持多种语言,如果你想创建一款翻译游戏,它会非常有用翻译成多种语言。Noto字体可https://www.google.com/get/noto/找到

The Noto fonts provided by Google Fonts provide support for many languages and can be very helpful if you want to create a game that is translated into multiple languages. The Noto fonts can be found at https://www.google.com/get/noto/.

美国信息交换标准代码( ASCII ) 是一组来自英语字符的字符集。ASCII 字符集的三种变体允许您在全集、仅大写或仅小写字符之间进行选择。可以在http://ascii.cl/找到ASCII字符列表

American Standard Code for Information Interchange (ASCII) is a set of characters from the English-language character set. The three variations of ASCII character sets allow you to choose between the full set, only uppercase, or only lowercase characters. You can find a list of the ASCII characters at http://ascii.cl/.

自定义字符集将允许您为自己的自定义字体导入自己的纹理图集。我发现当开发人员想要美化文本时,这种做法最常用,因为文本数量非常有限字符集,例如仅限数字。

A Custom set character will allow you to import your own texture atlas for your own custom font. I find this most commonly used when developers want beautified text with an extremely limited character set, such as numbers only.

上升计算模式

Ascent Calculation Mode

字体的上升距离字体基线和最高字形点之间的距离。没有关于如何确定这个假定的最高字形点的标准,因此 Unity 中提供了不同的模式可供选择,每种模式确定不同的最高字形点上升计算模式属性决定了如何计算上升。对于如何选择此计算,有三个选项:旧版本 2 模式(字形边界框)Face 上升度量Face 边界框度量。所选方法可能会影响字体的垂直对齐。

The ascent of a font is the distance between the font’s baseline and the highest glyph point. There is no standard for how this supposed highest glyph point is determined, so different modes are available in Unity to choose from, each determining a different highest glyph point. The Ascent Calculation Mode property determines how the ascent will be calculated. There are three options for how this calculation will be chosen: Legacy version 2 mode (glyph bounding boxes), Face ascender metric, and Face bounding box metric. The method chosen may affect the vertical alignment of the font.

旧版 2 模式(字形边界框)使用字体字符集中列出的任何字形所达到的最高点作为高度来测量上升。这仅使用字符集中列出的字形,并且并非所有字形都可能包含在该集合中。面上升度量使用定义为测量上升的面上升值,而面边界框度量使用面边界框来测量上升。

Legacy version 2 mode (glyph bounding boxes) measures the ascent using the highest point reached by any one of the font’s glyphs listed within its character set as the height. This only uses those listed in the character set, and not all glyphs may be included within that set. Face ascender metric uses the face ascender value that is defined to measure the ascent, whereas Face bounding box metric uses the face bounding box to measure the ascent.

排版比很多人想象的要复杂得多,这本书无法完全涵盖它——更不用说我不是排版专家。如果你想了解更多关于字形指标,可以在https://www.freetype.org/freetype2/d找到很好的介绍ocs/glyphs/glyphs-3.xhtml

Typography is a lot more complicated than many people realize, and it’s too complicated to fully cover in this book – not to mention I am no typography expert. If you’d like to learn more about glyph metrics, a good introduction can be found at https://www.freetype.org/freetype2/docs/glyphs/glyphs-3.xhtml.

动态字体设置

Dynamic font settings

当你导入字体时使用动态字符集,提供了两个新设置:包括字体数据字体名称

When you import your font with a dynamic character set, two new settings are made available: Include Font Data and Font Names.

包含字体数据会随游戏一起构建字体文件。如果未选择此选项,游戏将假定玩家已在其机器上安装该字体。如果您使用的是从网上下载的字体,那么可以肯定的是,最终用户不会安装该字体,因此您应该保留包含字体数据的选项。

Include Font Data builds the font file with the game. If this is not selected, the game will assume that the player has the font installed on their machine. If you are using a font you have downloaded from the web, it is a pretty safe bet that the end user will not have the font installed and you should leave Include Font Data selected.

字体名称是字体名称列表,如果 Unity 找不到字体,它将使用这个字体名称。如果字体不包含请求的字形或 Incl . Font Data属性被取消选择,并且用户的机器上没有安装该字体,则需要使用此字体名称。如果 Unity 找不到字体,它将在游戏的项目文件夹或用户的机器中搜索与字体名称中列出的名称之一匹配的字体。将字体输入到字体名称后,相应的字体将在项目中对其他字体的引用部分中列出

Font Names is the list of names of fonts that Unity will fall back on if it cannot find the font. It will need to fall back on this font name if the font doesn’t include the requested glyph or the Incl. Font Data property was deselected and the user does not have the font installed on their machine. If Unity cannot find the font, it will search the game’s project folder or the user’s machine for a font matching one of the names listed in Font Names. Once the fonts have been typed into Font Names, the appropriate fonts will be listed in the References to other fonts in project section:

图 10.22:Roboto-Regular 字体的检查器

图 10.22:Roboto-Regular 字体的检查器

Figure 10.22: The Inspector of the Roboto-Regular font

如果 Unity 找不到列出的字体之一,它将使用Unity 内硬编码的预定义后备字体列表中提供的字体。

If Unity cannot find one of the fonts listed, it will use a font provided in a predefined list of fallback fonts hard-coded within Unity.

有些平台没有系统中没有内置字体或无法访问内置字体。这些平台包括 WebGL 和一些控制台系统。在构建到这些平台时,Unity 将始终包含字体,无论设置选择如何sen包含字体数据

Some platforms don’t have built-in fonts in their system or can’t access built-in fonts. These platforms include WebGL and some console systems. When building to these platforms, Unity will always include fonts, regardless of the setting chosen for Include Font Data.

导入字体样式

Importing font styles

如果您引入了具有多个样式(即粗体、斜体或粗体和斜体),您必须引入所有字体样式才能正确显示。但是,将字体引入项目可能不足以使字体识别在选择粗体斜体粗体和斜体字体样式时应使用哪些字体。例如,以下屏幕截图显示了文本顶行具有粗体和斜体字体样式的Roboto-Regular Google 字体和文本底行具有普通字体样式的Roboto-BoldItalic Google 字体

If you bring in a font that has multiple styles (that is, bold, italic, or bold and italic), you must bring in all the font styles for them to appear properly. Bringing the fonts into your project may not be sufficient for the font to recognize which fonts should be used when the Bold, Italic, and Bold and Italic font styles are selected, however. For example, the following screenshot shows the Roboto-Regular Google Font with the Bold and Italic Font Style on the top line of text and the Roboto-BoldItalic Google Font with the Normal Font Style on the bottom line:

图 10.23:Roboto 字体的样式

图 10.23:Roboto 字体的样式

Figure 10.23: Styles of the Roboto font

如果Font Style属性正常工作,则两行应该匹配。但是,如您所见,它们不匹配。要使字体正确显示,请选择常规字体,重新输入Font Name属性以使所有适当的字体出现在字体列表中(如图 10.22所示),然后单击Apply 。执行此操作,两个字体应该看起来相同:

If the Font Style property were working correctly, the two lines should match. However, as you can see, they do not. To make the fonts appear correctly, select the regular font, retype the Font Name property to make all the appropriate ones appear in the font list (as shown in Figure 10.22), and hit Apply. After doing so, the two fonts should appear the same:

图 10.24:正确应用 Roboto 字体样式

图 10.24:正确应用 Roboto 字体样式

Figure 10.24: Styles of the Roboto font applied correctly

现在我们已经了解了如何导入字体,让我们看看如何创建自定义字体。

Now that we’ve reviewed how to import fonts, let’s look at how to create custom fonts.

自定义字体

Custom fonts

您可以创建自定义字体通过从项目窗口中选择创建|自定义字体。要使用自定义字体,您需要字体材质和字体纹理。本章的示例部分介绍了如何执行此操作。创建自定义字体后,您将获得以下要设置的属性

You can create a custom font by selecting Create | Custom Font from the project window. To use a custom font, you will need a font material and font texture. How to do this is covered in the Examples section of this chapter. Once you create your custom font, you will be given the following properties to set:

图 10.25:自定义字体的检查器

图 10.25:自定义字体的检查器

Figure 10.25: A custom font’s Inspector

行距属性指定每行文本之间的距离。

The Line Spacing property specifies the distance between each line of text.

Ascii Start Offset属性定义字体字符中的第一个 ASCII 字符集。例如,如果您创建的字体仅包含数字,则您的字符集将以数字0开头,即 ASCII 索引48。因此,您可以将ASCII 起始偏移量设置为48,表示此字体的字符集中的第一个字符是字符 0。您可以http://ascii.cl/上确定各个字符的 ASCII 索引号。

The Ascii Start Offset property defines the first ASCII character in the font’s character set. For example, if you created a font that only included numbers, your character set would start with the number 0, which is ASCII index 48. Therefore, you would set the ASCII Start Offset to 48, indicating the first character in this font’s character set is the character 0. You can determine the ASCII index number for individual characters at http://ascii.cl/.

Tracking属性表示整行文本的字符间距。它允许所有字符之间的间距均匀。

The Tracking property represents the spacing between characters for a full line of text. It allows the spacing between all characters to be uniform.

Kerning属性已被Tracking属性取代。Tracking和Kerning都是与字符间距相关的属性,但它们是不同的。有关更多信息信息,请查看http://www.practicalecommerce.com/Typography-101-The-Basics

The Kerning property has been replaced with the Tracking property. Tracking and Kerning are both properties related to spacing between characters, but they are different. For more information, check out http://www.practicalecommerce.com/Typography-101-The-Basics.

字符间距是字符之间的空间量,而字符填充是间距之前各个字符周围的填充量。

Character Spacing is the amount of space between characters, whereas Character Padding is the amount of padding surrounding individual characters before the spacing.

Character Rects属性决定了字体中总共有多少个字符。将数字从 0 更改为任何正数将提供可扩展的元素列表:

The Character Rects property determines how many total characters are in your font. Changing the number from 0 to any positive number will provide a list of Elements that can be expanded:

图 10.26:自定义字体的字符矩形

图 10.26:自定义字体的字符矩形

Figure 10.26: The Character Rects of a custom font

每个元素代表字符集上的一个字符。索引指定字符的 ASCII 索引

Each element represents a character on your character set. The Index is the ASCII index of the specified character.

UV宽度( W ) 和高度 ( H ) 值字体宽度的百分比以及字符所占的高度。例如,如果您的字体文件包含五列字符和两行字符,则W是五分之一或.2,而H将是二分之一或.5。这应该在所有字符中保持一致。X和Y值由WH值乘以字符所在的列或行来确定。

The UV width (W) and height (H) values of your font represent the percentage of the width and height your characters occupy. For example, if you had a font file with five columns of characters and two rows of characters, the W would be one-fifth or .2 and the H would be one-half or .5. This should be consistent throughout all of your characters. The X and Y values are determined by multiplying the W and H values by the column or row that the character is located in.

Vert属性表示字符的宽度和高度(以像素为单位)。H始终为负数。因此,如果字符的像素尺寸为 50 x 50,则WH值分别为50-50 。说实话,我不太清楚 height 属性为什么始终为负数,而且似乎找不到答案。我怀疑这与使用纹理坐标有关。Vert的XY值表示位置的变化,这些数字可以是负数也可以是正数。

The Vert properties represent the width and height of the character in pixels. The H value is always negative. So, if a character’s pixel dimension is 50 by 50, the W and H values would be 50 and -50, respectively. To be perfectly honest, I am not exactly sure why the height property is always negative and I can’t seem to find the answer. I suspect that it has something to do with the fact that this uses texture coordinates. The X and Y values of Vert represent a shift in position, where these numbers can be negative or positive.

前进设置表示特定字符​​和下一个字符之间的像素距离。

The Advance setting represents the pixel distance between the specific character and the next character.

翻转设置表示字形是否翻转或应如何显示。

The Flipped setting indicates if the glyph is flipped of how it should be displayed.

请参阅示例部分,获取计算自定义字体的UVVertAdvance值的示例

Please refer to the Examples section for an example of calculating the UV, Vert, and Advance values of a custom font.

在撰写本文时,Unity 的文档中并未完全定义自定义字体的所有属性,其中一些属性有点模糊。例如,Convert Case曾经是一个下拉菜单,我不清楚它现在如何使用,因为它只接受数字输入。也许,在未来,这些属性将得到更好的定义和手册将会更新以反映所做的新更改,网址为https://docs.unity3d.com/Manual/class-Font.xhtml

At the time of writing, not all the properties for custom fonts are fully defined within Unity’s documentation and a few of these properties are a bit ambiguous. For example, Convert Case used to be a dropdown menu, and it is unclear to me how it is now used since it only accepts a number input. Perhaps, in the future, these properties will be better defined and the manual will be updated to reflect the new changes made, at https://docs.unity3d.com/Manual/class-Font.xhtml.

字体资源

Font assets

回想一下,TextMeshPro 对象需要使用字体资源,而不是字体。因此,如果您为项目下载了一些字体,您将不会在 TextMeshPro 对象中看到它们作为可能的字体选项。如果您导入了 TextMeshPro 示例,除了 Liberation Sans 之外,您可能没有其他可用的选项。要在 TextMeshPro GameObject 中使用不同的字体,您不能简单地将新字体拖到Font Asset插槽中;您必须通过Font Asset Creator创建 Font Asset

Recall that TextMeshPro objects need to use font assets, not fonts. So, if you’ve downloaded some fonts for your project, you won’t see them as possible font options with a TextMeshPro object. If you’ve imported the TextMeshPro examples, you may have few options other than Liberation Sans available to you. To use a different font in a TextMeshPro GameObject, you cannot simply drag a new font into the Font Asset slot; you must create a Font Asset via the Font Asset Creator.

要访问Font Asset Creator,请选择Window | TextMeshPro - Font Asset Creator。这将允许您将字体文件转换为 TextMeshPro 可以使用的字体资产

To access the Font Asset Creator, select Window | TextMeshPro - Font Asset Creator. This will allow you to convert a font file into a font asset that can be used by TextMeshPro:

图 10.27:字体资产创建器窗口

图 10.27:字体资产创建器窗口

Figure 10.27: The Font Asset Creator window

有很多设置可以通过Font Asset Creator进行控制。您可以在 https://docs.unity3d.com/Packages/com.unity.textmeshpro@4.0/manual/FontAssetsCreator.xhtml?q=font%20asset%20creator 找到这些设置的详细说明。我将在示例部分介绍创建字体资产的过程本章

There are quite a few settings that can be controlled via the Font Asset Creator. You can find a breakdown of these settings at https://docs.unity3d.com/Packages/com.unity.textmeshpro@4.0/manual/FontAssetsCreator.xhtml?q=font%20asset%20creator. I will cover the process of creating a font asset in the Examples section of this chapter.

探索标记格式

Exploring the markup format

类似 HTML 的标记语言可以包含在TextTextMeshPro - Text (UI)组件的文本字段中以格式化文本。此标记格式类似于 HTML,因为它使用尖括号标记来包围要格式化的文本。标记使用<tag>要格式化的文本</tag>格式,您可以在其中用适当的标记替换标记。这些标记可以嵌套,就像在 HTML 中一样。

HTML-like markup language can be included within the text field of the Text or TextMeshPro - Text (UI) components to format the text. This markup format is HTML-like in that it uses angle bracket tags around the text that is to be formatted. The tags use the <tag>the text you wish to format</tag> format, where you replace tag with the appropriate tag. These tags can be nested, just as they can in HTML.

要在Text对象上使用标记格式,您必须首先在Text组件中选择Rich Text属性。默认情况下,它在TextMeshPro对象中起作用。

To use markup format on a Text object, you must first select the Rich Text property within the Text component. By default, it works within a TextMeshPro object.

此格式允许您更改字体样式、字体颜色和字体大小。下表列出了所需的标签执行指定的格式化:

This formatting allows you to change the font style, font color, and font size. The following chart lists the tags necessary to perform the specified formatting:

格式

Format

标签

Tag

大胆的

Bold

b

b

斜体

Italic

i

颜色

Color

颜色

color

尺寸

Size

尺寸

size

表 10.1:格式及其标签

Table 10.1: Formats and their tags

现在,让我们看看如何使用标记改变字体样式

Now, let’s look at how to change the style of a font with markup.

字体样式

Font style

您可以更改字体样式文本使用粗体和斜体标签。

You can change the font style of text using the bold and italic tags.

要为文本添加粗体字体样式,请在要加粗的文本周围添加<b></b>标签。要为文本添加斜体字体样式,请在要斜体的文本周围添加<i></i>标签

To add a bold font style to text, add the <b></b> tags around the text you wish to bold. To add an italic font style to text, add the <i></i> tags around the text you wish to italicize.

表 10.2:格式化标签示例

Table 10.2: Examples of formatting tags

现在,让我们看看如何使用标记改变字体的颜色。

Now, let’s look at how to change the color of a font with markup.

字体颜色

Font color

你可以改变颜色你的字体与十六进制值表示数字或使用颜色名称。要更改文本的颜色,请在要着色的文本周围添加<color=value></color>,其中可以放置十六进制值(在 # 后面)或出现单词值的颜色名称。

You can change the color of your font with either the hex value representation of a number or using the color name. To change the color of the text, add <color=value></color> around the text you wish to color, where you place either the hex value (following a #) or the color name where the word value appears.

只有有限的一组颜色具有可以替换十六进制值的名称。可识别的颜色名称包括黑色、蓝色、棕色、青色、深蓝色、绿色、灰色、浅蓝色、黄绿色、洋红色、栗色、深蓝色、橄榄色、橙色、紫色、红色、银色、青绿色、白色和黄色。您还可以使用浅绿色代替青色,使用紫红色代替洋红色

Only a limited set of colors have names that can replace the hex values. The color names that are recognized are black, blue, brown, cyan, darkblue, green, grey, lightblue, lime, magenta, maroon, navy, olive, orange, purple, red, silver, teal, white, and yellow. You can also use aqua in place of cyan and fuchsia in place of magenta.

使用颜色标签时,任何未被颜色标签包围的文本都将根据所选的颜色 属性进行着色。

When using the color tag, any text not surrounded by the color tag will be colored based on the Color property selected.

下表显示了如何使用颜色标签:

The following table shows how to use the color tag:

表 10.3:颜色格式标签示例

Table 10.3: Color formatting tag examples

现在,让我们看看如何使用标记改变字体的大小。

Now, let’s look at how to change the size of a font with markup.

字体大小

Font size

更改字体尺寸,在周围添加<size=#></size>标签您希望调整大小的文本。标签内未包含的任何文本将根据Font Size属性设置调整大小。以下示例显示如何使用size标签:

To change the font size, add the <size=#></size> tags around the text you wish to resize. Any text not within the tag will be sized based on the Font Size property setting. The following example shows how to use the size tag:

表 10.4:多个字体大小标签的示例

Table 10.4: Examples of multiple font size tags

到目前为止,我们已经了解了使用标记格式化字体的方法。现在,让我们看看如何使用样式表格式化字体。

So far, we’ve looked at the ways we can format fonts with markup. Now, let’s look at how to format a font using a style sheet.

使用样式表

Using style sheets

除了标记之外除了上一节中描述的标签之外,TextMeshPro 对象还可以使用样式表标签。您可能还记得10.9中,TextMeshPro - Text (UI)组件具有一个标记为“文本样式”的属性,其下拉列表中有十个选项。从那里,您可以选择下图所示的任何一种样式

In addition to the markup tags described in the preceding section, TextMeshPro objects can also use style sheet tags. As you may recall from Figure 10.9, the TextMeshPro - Text (UI) component has a property labeled Text Style with ten options in its dropdown. From there, you can select any one of the styles shown in the following figure:

图 10.28:默认样式表中的各种默认样式

图 10.28:默认样式表中的各种默认样式

Figure 10.28: The various default styles from the default style sheet

所有这些样式都是预定义的通过为项目设置中的TextMeshPro 设置分配的默认样式表选项(正如我们在TextMeshPro 项目设置部分中所讨论的)。

All of these styles are pre-defined by the Default Style Sheet option that’s assigned for the TextMeshPro Settings within the Project Settings (as we discussed in the TextMeshPro Project Settings section).

图 10.29:项目设置中的默认样式表设置

图 10.29:项目设置中的默认样式表设置

Figure 10.29: The Default Style Sheet setting from the Project Settings

单击项目设置中的默认样式表(TMP_Style Sheet)对象将选择资产文件夹中的默认样式表

Clicking on the Default Style Sheet (TMP_Style Sheet) object within the Project Settings will select the Default Style Sheet within the Assets folder:

图 10.30:资源中的默认样式表资产

图 10.30:资源中的默认样式表资产

Figure 10.30: The Default Style Sheet asset within Resources

然后,您可以查看默认样式表资产的检查器,并查看每个默认样式sheet标签定义如下:

You can then view the Default Style Sheet asset’s Inspector and see how each of the default style sheet tags are defined:

图 10.31:默认样式表属性

图 10.31:默认样式表属性

Figure 10.31: The Default Style Sheet properties

您可以通过编辑此资产随意更改任何这些标签的名称和属性。您可以添加和删除标签。

You are free to change the names and properties of any of these tags by editing this asset. You can add and remove tags.

您还可以创建一个全新的样式表并将其设为默认样式表。要创建新的样式表,请在Assets文件夹中单击鼠标右键,然后选择“创建” | “TextMeshPro” | “样式表”

You can also create a whole new style sheet and make it the default style sheet. To create a new style sheet, right-click within the Assets folder and select Create | TextMeshPro | Style Sheet.

您可以通过下拉菜单应用这些样式,正如我在本节开头所演示的那样,或者您可以使用<style>标签通过标记格式将它们分配给文本的各个部分。例如,如果您想显示图 10.32所示的文本,您可以这样做在文本字段中输入以下<style="Title">如何</style>使用<style="Link">内联</style>样式!

You can apply these styles via the dropdown, as I demonstrated at the beginning of this section, or you can assign them to portions of the text via markup format using the <style> tag. For example, if you wanted to display the text shown in Figure 10.32, you could do so by typing Here's <style="Title">how</style> to use styles <style="Link">in-line</style>! into the Text field:

图 10.32:以标记方式内联使用的样式

图 10.32:以标记方式内联使用的样式

Figure 10.32: Styles used in-line in a markup fashion

现在我们已经回顾了如何格式化字体和标记,让我们继续探索如何使用各种文本组件属性以及翻译。

Now that we’ve reviewed the how to format fonts and with markup, let’s move on to explore how you can use the various Text component properties, with translations.

电视翻译文本

Translating text

如果你正在创建如果您的游戏中包含文字,您可能需要翻译该文字。为了确保您的游戏易于翻译,您可以采取一些关键措施,让游戏更容易地转换到不同的语言。

Odds are, if you are creating a game with text in it, you might want to translate that text. To make sure your games are easily translatable, there are a few key things that you can do to make the transition to different languages easier.

您要确保,如果翻译后的文本变长或变短,它仍能适合必要的区域。您可以使用文本组件的“按几何对齐”“最佳拟合”属性来实现这一点。这将使文本适合所需的空间。您还可以使用内容大小调整器(我们在第 7 章中讨论过)来确保文本周围的任何面板都会缩小或扩大,以完美适合文本。如果您不介意不同语言的字体大小不同,则可以使用第一个选项。如果您希望字体大小在不同语言中保持一致,可以使用第二个选项。请记住,某些语言可以在非常小的空间内呈现单个短语,而其他语言则需要在非常大的空间内呈现它。

You want to make sure that the text you plan to translate will still fit within the necessary area if it gets longer or shorter when translated. You can accomplish this by using the Align By Geometry and Best Fit properties of the Text component. This will make the text fit within the required space. You could also use the Content Size Fitter (which we discussed in Chapter 7) to make sure any Panels around the text will shrink or expand to fit perfectly around the text. You can use the first option if you don’t mind the font size varying across languages. You can use the second if you want the font size to remain consistent across languages. Keep in mind that some languages can render a single phrase in a very small amount of space, while others will render it in a very large amount of space.

使用可以支持您要翻译的语言的字体。如果可能的话,最好使用一种适用于所有语言的字体,因为它将在所有翻译中保持一致的风格。您花了很多时间挑选完美的字体!您不希望在翻译游戏时将所有这些都抛到九霄云外,并且字体无法呈现该语言的所有字形!它不是一种华丽或特别时尚的字体,但可以翻译成多种语言的是 Noto Sans 字体系列。如果您知道您将要翻译成多种语言,您可能需要考虑它:https ://fonts.google.com/noto/fonts 。

Use fonts that can support the languages you will be translating to. If possible, a single font that works across all languages will be preferred as it will maintain a consistent style across all translations. You spent so much time picking the perfect font! You don’t want all that to be thrown out the window when you translate your game and the font won’t render all the glyphs of the language! It’s not a glamorous or particularly stylish font, but one that will translate into many languages is the Noto Sans font family. If you know you’ll be translating into a lot of languages, you might want to consider it: https://fonts.google.com/noto/fonts.

如果您计划翻译成从右到左呈现的语言,例如阿拉伯语,则需要使用 TextMeshPro 对象而不是 Text 对象,因为它允许您从 RTL 呈现文本。

If you plan to translate into a language that renders right-to-left, such as Arabic, you will need to use a TextMeshPro object, rather than a Text object, as it will let you render text from RTL.

笔记

Note

在撰写本文时,TextMeshPro 虽然能够从右到左渲染文本,但并不完全支持从右到左的语言。如果您想在游戏的 UI 中显示阿拉伯语、波斯语和/或希伯来语,我推荐以下软件包:https ://github.com/pnarimani/RTLTMPro 。

At the time of writing, TextMeshPro, while able to render text from right-to-left, does not fully support right-to-left languages. If you’d like to display Arabic, Farsi, and/or Hebrew in your game’s UI, I recommend the following package: https://github.com/pnarimani/RTLTMPro.

您可能还会喜欢以下有关渲染从右到左文本的教程: https: //allcorrectgames.com/insights/unity-from-right-to-left/

You may also appreciate the following tutorial on rendering right-to-left text: https://allcorrectgames.com/insights/unity-from-right-to-left/.

以下内容也非常有用的资源,因为它详细介绍了如何为您的项目构建本地化解决方案,并讨论了从右到左的文本翻译:https ://phrase.com/blog/posts/localizing-unity-games-official-localization-package/ 。

The following is also an extremely helpful resource as it breaks down how to architect a localization solution for your project and also discusses right-to-left text translation: https://phrase.com/blog/posts/localizing-unity-games-official-localization-package/.

示例部分,我提供了一个专注于翻译的 UI 布局和字体方面的小示例

In the Examples section, I provide a small example that focuses on the UI layout and font aspects of translation.

示例

Examples

在本章中,我们将进一步扩展我们已经构建的场景,并添加一个发生在开始屏幕和主游戏屏幕之间的新场景

In this chapter, we’ll expand on the scene we’ve been building further and also add a new scene that occurs between our start screen and our main game screen.

创建动画文本

Creating animated text

首先,我们将创建一个新的场景,它就像我们的开始屏幕和游戏场景之间的过场动画。它将包括我们的猫自我介绍。文本将像正在输入一样动画,用户可以选择通过按下按钮来加快速度。文本完全显示后,按下同一个按钮将显示下一个文本块或转到游戏场景。文本窗口将显示如下:

First, we will create a new scene that acts like a cut scene between our start screen and our gameplay scene. It will include our cat introducing itself. The text will animate as if it is being typed, and the user will have the option to speed it up by pressing a button. Once the text is fully displayed, pressing that same button will either show the next block of text or go to the gameplay scene. The text windows will appear as follows:

图 10.33:动画文本框的最终结果

图 10.33:动画文本框的最终结果

Figure 10.33: The end result of our animated text box

让我们首先创建一个预制件,以节省一些开发时间

Let’s start by creating a prefab to save us some development time.

创建背景画布预制件和新场景

Creating a Background Canvas prefab and a new scene

在我们能够开始制作动画文本,我们需要构建场景。在迄今为止创建的两个场景中,我们都使用了Background Canvas来显示背景图像,我们将在新场景中再次使用它。

Before we can start making animated text, we need to build out our scene. In both the scenes we have created so far, we used Background Canvas to display the background image, and we will use it again in a new scene.

由于我们将多次使用此背景画布,因此我们应该创建一个背景画布预制件。正如我们了解到的在上一章中,预制件是可重复使用的游戏对象。在场景中使用预制件会在场景内创建预制件的实例。如果您对已保存的预制件进行更改,则更改将反映在所有场景中所有未中断的预制件实例中。

Since we will use this Background Canvas multiple times, we should create a Background Canvas prefab. As we learned in a previous chapter, a prefab is a reusable GameObject. Using a prefab in a scene creates an instance of the prefab within the scene. If you make a change to the saved prefab, the change will be reflected in all unbroken prefab instances across all scenes.

要创建可重复使用的背景画布预制游戏对象,请完成以下步骤:

To create a reusable Background Canvas prefab GameObject, complete the following steps:

  1. 打开第 9 章示例场景。
  2. Open the Chapter9-Examples scene.
  3. Background Canvas游戏对象从 Hierarchy拖到Project视图中的Prefabs文件夹中。现在Hierarchy中的Background Canvas名称应为蓝色(表示它是一个预制件)。
  4. Drag the Background Canvas GameObject from the Hierarchy into the Prefabs folder within the Project view. The name Background Canvas should now be blue in the Hierarchy (symbolizing that it is a prefab).
  5. 让我们创建一个新场景,在其中我们将使用此Background Canvas预制件。创建一个名为Chapter10-Examples-IntroScene的新场景并将其保存在Scenes文件夹中。
  6. Let’s create a new scene in which we will use this Background Canvas prefab. Create a new scene called Chapter10-Examples-IntroScene and save it in the Scenes folder.
  7. Background Canvas.prefab从Project视图拖到新场景中
  8. Drag the Background Canvas.prefab into the new scene from the Project view.
  9. 将主摄像头分配给背景画布画布组件的渲染摄像头插槽,并使确保已设置排序层 背景
  10. Assign the Main Camera to the Render Camera slot of the Canvas component on the Background Canvas and make sure that Sorting Layer is set to Background.

现在,我们可以开始设置保存动画的窗口文本。

Now, we can start setting up the windows that will hold our animated text.

布局文本框窗口

Laying out the text box windows

创建文本框窗口显示我们的文本,完成以下步骤:

To create the text box windows that will display our text, complete the following steps:

  1. 创建一个新的 UI Canvas 并将其命名为 Text Canvas。设置其CanvasCanvas Scalar属性,如下所示:
  2. Create a new UI Canvas and name it Text Canvas. Set its Canvas and Canvas Scalar properties, as shown here:
图 10.34:文本画布检查器

图 10.34:文本画布检查器

Figure 10.34: The Text Canvas Inspector

  1. 创建新的 UI 图像并将其命名为TextHolder1。将其锚点和枢轴设置为中间中心。将uiElements_10分配给源图像,然后选择设置本机大小以将图像的大小设置为 223 x 158。
  2. Create a new UI Image and name it TextHolder1. Set its anchor and pivot to middle center. Assign uiElements_10 to the Source Image, and then select Set Native Size to cause the image’s size to be set to 223 x 158.
  3. 此面板将保存我们的猫的图像、一些文本和一个继续按钮。利用锚点、枢轴和拉伸,将 UI 对象布局为TextHolder1的子项,使其显示如下:
    图 10.35: TextHolder1 及其子项

    图 10.35: TextHolder1 及其子项

    请注意,在上面的屏幕截图中,Text子项的 Rect Transform 不会完全延伸到TextHolder1的图像。这样,文本就不会越过窗口的白色区域。

  4. This Panel will hold an image of our cat, some text, and a continue button. Utilizing anchors, pivots, and stretching, lay out the UI objects as children of TextHolder1 so that they appear as illustrated:

    Figure 10.35: TextHolder1 and its children

    Note that in the preceding screenshot, the Text child’s Rect Transform does not stretch all the way across the image of TextHolder1. That way, the text won’t cross over the white area of the window.

  1. 我们希望能够要打开或关闭此窗口,请向TextHolder1添加一个Canvas Group组件。保留其默认值。
  2. We’ll want to be able to turn this window on and off, so add a Canvas Group component to TextHolder1. Leave the settings at their default values.
  3. 现在让我们改变字体。前往 https://www.dafont.com/milky-coffee.font并下载名为Milky Coffee 的 字体
  4. Now, let’s change the font. Go to https://www.dafont.com/milky-coffee.font and download the font named Milky Coffee.
  5. 将Milky Coffee字体添加到您的Assets/Fonts文件夹,然后将其拖到Text对象的Text组件的字体设置中
  6. Add the Milky Coffee font to your Assets/Fonts folder and then drag it into the Font setting of the Text object’s Text component.
  7. 将字体大小更改 18
  8. Change the font Size to 18.
  9. 复制TextHolder1并将副本命名为TextHolder2
  10. Duplicate TextHolder1 and name the duplicate TextHolder2.
  11. 将TextHolder1Text子项上的文本替换为“Hello there!” ,将TextHolder2Text子项上的文本替换为“我是一只猫,出于某种原因,我正在收集食物!”
  12. Replace the text on the Text child of TextHolder1 with "Hello there!" and the text on the Text child of TextHolder2 with "I'm a cat and, for some reason, I'm collecting food!".
  13. 让我们通过禁用Interactable、禁用Blocks Raycasts并设置来隐藏和禁用TextHolder2将Canvas Group组件上的Alpha值设为0
  14. Let’s hide and disable TextHolder2 by disabling Interactable, disabling Blocks Raycasts, and setting the Alpha value to 0 on the Canvas Group component.

现在,我们可以开始制作动画了我们的文本!

Now, we’re ready to start animating our text!

为文本框文本添加动画效果

Animating the text box text

现在我们有了布局设置完成后,我们可以为文本添加动画。为此,我们需要创建一个新脚本。该脚本将控制文本的动画,并在显示完所有文本后加载下一个场景

Now that we have our layout set up, we can animate our text. To do this, we will need to create a new script. This script will control the animation of the text, as well as load the next scene after all the text has been displayed.

要创建看起来像是打字的动画文本,请完成以下步骤:

To create animated text that looks like it’s typing out, complete the following steps:

  1. 我们需要将三项内容组合起来以创建动画文本框:包含文本的面板、将显示消息的文本对象以及我们要显示的字符串。因此,要将它们全部组合起来,我们需要创建一个类。创建一个名为DialogueBox的新类。
  2. There are three things we want to group to create the animated text box: the Panel that holds the text, the text object that will display the message, and the string that we want to display. So, to group them all, we will need to create a class. Create a new class called DialogueBox.
  3. 更新脚本,使其不从MonoBehaviour继承,如下所示:
    使用 UnityEngine;
    使用 UnityEngine.UI;
    [系统.可序列化]
    公共类对话框
    {
        公共 CanvasGroup 文本持有者;
        公共文本textDisplayBox;
        公共字符串对话;
    }

    我使用了[System.Serializable],这样我们就能在 Inspector 中看到这些值。请记住,每当我们使用Text类型时,我们都需要使用UnityEngine.UI命名空间

  4. Update the script so that it does not inherit from MonoBehaviour and looks as follows:
    using UnityEngine;
    using UnityEngine.UI;
    [System.Serializable]
    public class DialogueBox
    {
        public CanvasGroup textHolder;
        public Text textDisplayBox;
        public string dialogue;
    }

    I’ve used [System.Serializable] so that we will be able to see these values in the Inspector. Remember we need to use the UnityEngine.UI namespace whenever we use a Text type.

  5. 创建一个名为DialogueSystem.cs的新 C# 脚本。
  6. Create a new C# script called DialogueSystem.cs.
  7. 我们将编写代码来实现场景加载并使用各种系统方法和集​​合。因此,我们需要在新脚本的顶部包含以下命名空间
    使用系统;
    使用System.Collections;
    使用 System.Collections.Generic;
    使用 UnityEngine;
    使用 UnityEngine.SceneManagement;
  8. We will be writing code that implements scene loading and uses various System methods and collections. Therefore, we need to include the following namespaces at the top of our new script:
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.SceneManagement;
  9. 现在,让我们开始变量声明。首先,我们将创建一个名为dialogBoxes的DialogueBox对象列表
    公共列表<DialogueBox> dialogBoxes;
  10. Now, let’s begin our variable declaration. First, we will create a list of DialogueBox objects called dialogueBoxes:
    public List<DialogueBox> dialogueBoxes;
  11. 创建一个私有变量,用于保存文本动画完成后加载的场景的名称
    [SerializeField] 字符串 nextScene;

    我用SerializeField属性标记它,以便它可以通过 Inspector 分配,同时仍然是私有的。

  12. Create a private variable that will hold the name of the scene that loads after the text is done animating:
    [SerializeField] string nextScene;

    I marked it with the SerializeField attribute so that it can be assigned via the Inspector while still being private.

  13. 在继续之前,让我们分配在 Unity 编辑器中设置这些变量。将DialogueSystem.cs脚本附加到Text Canvas游戏对象。您应该看到以下内容:
  14. Before we proceed, let’s assign these variables in the Unity Editor. Attach the DialogueSystem.cs script to the Text Canvas GameObject. You should see the following:
图 10.36:对话系统组件

图 10.36:对话系统组件

Figure 10.36: The Dialogue System component

  1. 按两次对话框列表底部的加号。由于我们使DialogueBox类可序列化,因此我们应该看到以下内容:
  2. Press the plus sign at the bottom of the Dialogue Boxes list twice. Since we made the DialogueBox class serializable, we should see the following:
图 10.37:包含两个元素的对话系统组件

图 10.37:包含两个元素的对话系统组件

Figure 10.37: The Dialogue System component with two Elements

  1. 现在,让我们将适当的元素拖到适当的字段中。将TextHolder1拖到元素 0文本框中,将TextHolder2拖到元素 1文本框中
  2. Now, let’s drag the appropriate elements into the appropriate fields. Drag TextHolder1 into Element 0’s Text Holder and TextHolder2 into Element 1’s Text Holder.
  3. TextHolder1文本子对象拖入元素 0文本显示框槽中,将TextHolder2文本子对象拖入元素 1文本显示框中
  4. Drag the Text child object of TextHolder1 into Element 0’s Text Display Box slot and the Text child object of TextHolder2 into Element 1’s Text Display Box.
  5. 现在,将元素 0元素 1对话分别更新为“你好!”和“我是一只猫,出于某种原因,我正在收集食物!”。我们将通过代码生成文本框中的文本。在上一小节中,我们将文本添加到我们放置在场景中的文本框中。但是,由于我们将要编写的代码,该步骤将变得不必要。不过,将文本添加到这些文本框中很有帮助,因为我们能够准确地看到它将如何显示。
  6. Now, update the Dialogue of Element 0 and Element 1 to Hello there! and I'm a cat and, for some reason, I'm collecting food! respectively. We will be generating the text within the text boxes via code. In the previous subsection, we added the text to the text boxes that we placed in the scene. However, due to the code we will write, that step will be rendered unnecessary. It was helpful adding the text to those text boxes, though, because we were able to see exactly how it will display.
  7. 目前,我们需要在编辑器中做的最后一件事是分配对话完成后游戏将导航到的场景。稍后我们将创建一个名为Chapter10-Examples 的场景,但现在,我们只需通过键入Chapter9-Examples 将 Chapter9- Examples场景分配给下一个场景插槽即可。完成的组件应如下所示:
  8. The last thing we need to do in the Editor, for now, is assign the scene that the game will navigate to once the dialogue has completed. We’ll create a scene called Chapter10-Examples later, but, for now, let’s just assign the Chapter9-Examples scene to the Next Scene slot by typing Chapter9-Examples. Your completed component should look as follows:
图 10.38:已填写所有属性的对话系统组件

图 10.38:已填写所有属性的对话系统组件

Figure 10.38: The Dialogue System component with all its properties filled out

  1. 现在我们可以回去了添加到我们的DialogueSystem.cs脚本中。我们的变量声明还没有完成。我们需要两个变量来跟踪我们希望显示的对话中的字符串,以及我们要显示的特定字符串中的哪个字符。将以下变量声明添加到您的脚本中:
    int哪个文本 = 0;
    int 字符串中的位置 = 0;

    请注意,这两个变量不是公共的或序列化的,因此无法在 Inspector 中调整。它们的值将由脚本调整。whichText变量允许我们在显示对话列表中的第一个字符串和第二个字符串之间切换。我们将编写的代码将很容易扩展到更多字符串。positionInString变量将跟踪动画正在输入哪个字符。我们希望跟踪这一点,以便我们可以判断用户是否正在加速文本,或者他们是否已经阅读了整个文本并只想继续下一部分

  2. Now, we can go back to our DialogueSystem.cs script. We aren’t quite done with our variable declaration. We need two variables that will track the string in our dialogue we wish to display, as well as which character within that specific string we are displaying. Add the following variable declaration to your script:
    int whichText = 0;
    int positionInString = 0;

    Note that these two variables are not public or serialized and thus cannot be adjusted in the Inspector. Their values will be adjusted by the script. The whichText variable will allow us to switch between displaying the first string in the dialogue list and the second. The code we will write will easily be extendable to more strings. The positionInString variable will keep track of which character is being typed out by the animation. We want to keep track of this so that we can tell whether the text is being sped up by the user or if they have already read the whole text and just want to proceed to the next part.

  3. 我们将使用协程来为文本逐个字母添加动画效果。协程非常适合定时和计划事件。我们需要声明的最后一个变量将允许我们引用协程,以便我们轻松停止它。声明以下变量:
    协同程序textPusher;
  4. We will use a coroutine to animate our text one letter at a time. Coroutines work very well for timed and scheduled events. The last variable we need to declare will allow us to reference our coroutine so that we can easily stop it. Declare the following variable:
    Coroutine textPusher;
  5. 控制文本动画的协程如下:
    IEnumerator 写入文本()
    {
        对于(int i = 0; i <= dialogBoxes[whichText].dialogue.Length; i++)
        {
            对话框[whichText].textDisplayBox.text = 对话框[whichText].对话.Substring(0, i);
            位置在字符串++;
            产生返回新的WaitForSeconds(0.1f);
        }
    }

    此代码使用whichText变量在当前对话框中查找当前字符串,并循环遍历其所有字符。在循环的每个步骤中,UI Text 对象的 text 属性都会更新以显示字符串的前i 个字符,其中i表示循环的当前步骤。然后,它增加positionInString变量并等待十分之一秒,然后继续循环的下一步以显示下一个字符

  6. The coroutine that will control the text animation is as follows:
    IEnumerator WriteTheText()
    {
        for (int i = 0; i <= dialogueBoxes[whichText].dialogue.Length; i++)
        {
            dialogueBoxes[whichText].textDisplayBox.text = dialogueBoxes[whichText].dialogue.Substring(0, i);
            positionInString++;
            yield return new WaitForSeconds(0.1f);
        }
    }

    This code finds the current string in the current dialogue box using the whichText variable and loops through all of its characters. With each step of the loop, the text property of the UI Text object is updated to display the first i characters of the string, where i represents the current step of the loop. It then increases the positionInString variable and waits a tenth of a second to display the next character by proceeding to the next step in the loop.

  7. 在前面代码会做任何事情,我们需要启动我们的协程。在启动它的过程中,我还想分配textPusher变量。将以下内容添加到Start()函数中:
    无效开始()
    {
        textPusher = StartCoroutine(WriteTheText());
    }

    如果你现在玩游戏,你应该会看到场景中出现“Hello there!”文本。

  8. Before the preceding code will do anything, we need to start our coroutine. In the process of starting it, I also want to assign the textPusher variable. Add the following to the Start() function:
    void Start()
    {
        textPusher = StartCoroutine(WriteTheText());
    }

    If you play your game now, you should see the Hello there! text type out within your scene.

  9. 目前,协程仅循环遍历第一个DialogueBox中的字符串。我们需要通过增加whichText变量使其继续执行列表中的DialogueBox。我们还需要添加功能以允许玩家显示所有文本,这样他们就不必等待它完全动画化(如果他们不耐烦的话)。让我们创建一个按下按钮时会调用的函数。我们还将创建一个函数来划分Canvas Group 的启用和禁用:
    公共无效ProceedText()
    {
        如果(positionInString < dialogBoxes [whichText] .dialogue.Length)
        {
            停止协同程序(textPusher);
            对话框[哪个文本].textDisplayBox.文本 = 对话框[哪个文本].对话;
            positionInString = dialogBoxes[whichText].dialogue.Length;
        }
        别的
        {
            切换CanvasGroup(dialogueBoxes[whichText].textHolder,false);
            哪个文本++;
            如果(whichText> = dialogBoxes.Count)
            {
                场景管理器.加载场景(下一个场景);
            }
            别的
            {
                字符串中的位置 = 0;
                切换CanvasGroup(dialogueBoxes[whichText].textHolder,true);
                textPusher = StartCoroutine(WriteTheText());
            }
        }
    }
    公共无效ToggleCanvasGroup(CanvasGroup面板,bool显示)
    {
        Panel.alpha = 显示?1:0;
        面板.interactable = 显示;
        面板.blocksRaycasts = 显示;
    }

    当按钮按下后,代码首先使用positionInString变量确定是否已显示整个字符串。如果positionInString变量小于当前字符串中的字符总数,则显示完整字符串;否则,继续。

    positionInString变量小于当前字符串中的字符总数时,协程将使用StopCoroutine(textPusher)提前停止。

    textDisplayBoxtext属性被更新以显示完整的字符串,并且positionInString被设置为字符串的长度;这样,如果再次单击该按钮,此函数就会知道它可以继续下一步

    positionInString变量不小于当前字符串中的字符总数时,当前 Canvas Group 将被停用,然后whichText变量将增加。一旦此变量增加,代码将检查是否还有更多文本框需要动画。如果没有,则加载下一个场景。如果有更多文本框需要动画,则positionInString变量将重置为0,因此将首先显示字符串中的第一个字符。现在激活了新的 Canvas Group,并重新分配了textPusher变量,以便再次播放协程循环

  10. Currently, the coroutine only loops through the string in the first DialogueBox. We need to make it proceed to DialogueBox in the list by increasing the whichText variable. We also need to add functionality to allow players to show all the text so that they don’t have to wait for it to fully animate if they’re impatient. Let’s create a function that will be called by the press of our buttons. We’ll also create a function that compartmentalizes the enabling and disabling of a Canvas Group:
    public void ProceedText()
    {
        if (positionInString < dialogueBoxes[whichText].dialogue.Length)
        {
            StopCoroutine(textPusher);
            dialogueBoxes[whichText].textDisplayBox.text = dialogueBoxes[whichText].dialogue;
            positionInString = dialogueBoxes[whichText].dialogue.Length;
        }
        else
        {
            ToggleCanvasGroup(dialogueBoxes[whichText].textHolder, false);
            whichText++;
            if (whichText >= dialogueBoxes.Count)
            {
                SceneManager.LoadScene(nextScene);
            }
            else
            {
                positionInString = 0;
                ToggleCanvasGroup(dialogueBoxes[whichText].textHolder, true);
                textPusher = StartCoroutine(WriteTheText());
            }
        }
    }
    public void ToggleCanvasGroup(CanvasGroup Panel, bool show)
    {
        Panel.alpha = show ? 1 : 0;
        Panel.interactable = show;
        Panel.blocksRaycasts = show;
    }

    When a button is pressed, the code first determines whether the whole string has been displayed using the positionInString variable. If the positionInString variable is smaller than the total characters in the current string, it displays the complete string; otherwise, it proceeds.

    When the positionInString variable is less than the total characters in the current string, the coroutine is stopped early with StopCoroutine(textPusher).

    The text property of the textDisplayBox is updated to display the full string, and the positionInString is set to the length of the string; that way, if the button is clicked again, this function will know that it can proceed to the next step.

    When the positionInString variable is not less than the total characters in the current string, the current Canvas Group is deactivated, and then the whichText variable is increased. Once this variable is increased, the code checks whether any more text boxes need animating. If there are not, the next scene loads. If more text boxes need animating, the positionInString variable is reset to 0, so the very first character in the string will be displayed first. The new Canvas Group is now activated, and the textPusher variable is reassigned so that the coroutine loop will play again.

  11. 现在我们的代码已经完成,我们只需要连接按钮来执行上一步中描述的功能。对于两个TextHolder对象上的两个按钮,将On Click()事件设置为运行附加到Text Canvas 的DialogueSystem脚本中的ProceedText()函数。现在,当您玩游戏时,在文本未完成输入时单击按钮将导致其完全显示,而在文本完全显示后单击按钮将显示下一个对话或下一个场景
  12. Now that our code is done, we just need to hook up our buttons to perform the function described in the previous step. For both buttons on both TextHolder objects, set the On Click() event to run the ProceedText() function in the DialogueSystem script attached to the Text Canvas. Now, when you play the game, clicking on the button when the text isn’t finished typing will cause it to fully display, and clicking on the button when the text has fully displayed will display the next dialogue or the next scene.

为了改进这一点,你还可以创建TextHolder对象的预制件,并编写基于Dialogue List实例化到场景中的代码。我建议实施这一改变如果你要制作一个更复杂的对话系统。

To improve on this, you can also create a prefab of the TextHolder object and write code that instantiates into the scene based on Dialogue List. I recommend implementing this change if you will be making a more complicated dialogue system.

笔记

Note

本书代码包中提供的代码示例包括代码注释,由于太杂乱,无法显示,因此未在此处显示在这篇文章中。

The code example provided in this book’s code bundle includes code comments not shown here as it was too cluttered to display in this text.

翻译对话

Translating the dialogue

让我们扩展一下我们的动画文本示例,以便包含翻译。这是一个基本示例,用于演示如何访问文本组件的某些属性,其架构方式不一定适合大型项目。

Let’s expand upon our animated text example so that it includes translations. This is a basic example to demonstrate how to access certain properties of Text components and isn’t necessarily architected in a way that would be sustainable for a large project.

笔记

Note

请注意,我使用谷歌翻译来获得这些翻译,因此它们可能不完全准确:

Please note that I used Google Translate to obtain these translations, so they may not be fully accurate:

图 10.39:谷歌翻译

Figure 10.39: Google Translate

为动画添加翻译我们之前完成的文本示例,请完成以下步骤:

To add translation to the animated text example we completed previously, complete the following steps:

  1. 打开DialogueBox.cs脚本,并在脚本顶部添加以下命名空间:
    使用 System.Collections.Generic;
  2. Open the DialogueBox.cs script and add the following namespace to the top of the script:
    using System.Collections.Generic;
  3. 让我们向此脚本添加一个子类,以便将所有翻译与适当的设置进行分组。我们将使用此子类保存有关翻译文本和适当字体的信息:
    [系统.可序列化]
    公共课翻译
    {
        公共字符串语言键;
        公共字符串翻译字符串;
        公共字体字体;
        公共字体样式字体样式;
    }

    languageKey字符串将用作查找适当翻译的关键字。

  4. Let’s add a subclass to this script that will group all the translations with the appropriate settings. We’ll use this subclass to hold information about not only the translated text but the appropriate font:
    [System.Serializable]
    public class Translation
    {
        public string languageKey;
        public string translatedString;
        public Font font;
        public FontStyle fontStyle;
    }

    The languageKey string will be used as a key to finding the appropriate translation.

  5. 现在,添加一个包含所有翻译的列表:
    公共列表<Translation>翻译;

    您的DialogueSystem组件现在应更新为如下所示:

  6. Now, add a list that will hold all of the translations:
    public List<Translation> translations;

    Your DialogueSystem component should now be updated to look like the following:

图 10.40:带有翻译的对话系统组件

Figure 10.40: The Dialogue System component with translations

  1. 让我们添加一些翻译翻译列表中。我们将使用 ISO 639-1 两位数代码作为每种语言的键。将以下四个键代码添加到翻译列表中以指示西班牙语、日语、简体中文和韩语:
  2. Let’s add a few translations to the translations list. We’ll use the ISO 639-1 two-digit codes as keys for each language. Add the following four key codes to the translation list to indicate Spanish, Japanese, Simplified Chinese, and Korean:
图 10.41:对话框中填写的翻译键

图 10.41:对话框中填写的翻译键

Figure 10.41: The translation keys filled out on the Dialogue Boxes

  1. 为了节省您输入的时间这些键,通过右键单击单词Translations并选择Copy来复制此列表。然后,在I'm a cat and, for some cause, I'm gathering food!元素下,通过右键单击并选择Paste将此列表粘贴到其Translations中
  2. To save yourself time with entering these keys, copy this list by right-clicking on the word Translations and selecting Copy. Then, under the I'm a cat and, for some reason, I'm collecting food! element, paste this list to its Translations by right-clicking and selecting Paste.
  3. 现在,让我们输入翻译。在翻译字符串属性中输入以下数据:

    钥匙

    你好呀!

    我是一只猫,出于某种原因,我正在收集食物!

    西文

    你好

    Soy un gato y, por alguna razón, estoy recolectando comida!

    谢谢你!

    私は猫で、なぜか食べ物を集めています!

    中文

    你好呀!

    我是一只猫,由于某种原因,我正在收集食物!

    好的

    亲爱

    表 10.5:要输入的翻译字符串

    你会注意到 Unity 引擎能够在Inspector 中呈现这些语言。

  4. Now, let’s enter the translations. Enter the following data into the Translated String properties:

    Key

    Hello there!

    I’m a cat and, for some reason, I’m collecting food!

    es

    ¡Hola!

    ¡Soy un gato y, por alguna razón, estoy recolectando comida!

    ja

    こんにちは!

    私は猫で、なぜか食べ物を集めています!

    zh

    你好呀!

    我是一只猫,出于某种原因,我正在收集食物!

    ko

    안녕!

    나는 고양이고, 왠지 모를 음식을 모으고 있다!

    Table 10.5: The translation strings to enter

    You’ll notice that the Unity Engine is capable of rendering these languages in the Inspector.

  1. 现在我们需要为每种语言分配一些字体。当前的字体Milky Coffee支持这四种语言。如果我们尝试用其中一种翻译替换文本,引擎将使用支持它的字体渲染任何不支持的字形,但使用Milky Coffee字体渲染所有支持的字形(例如标点符号)。这将导致不一致的样式,老实说看起来并不好。例如,如下图所示,逗号和感叹号与文本的其余部分不使用相同的字体:
    图 10.42:以两种字体呈现韩文文本的对话框

    图 10.42:以两种字体呈现韩文文本的对话框

    因此,我们希望每当需要翻译时,就将字体更改为我们专门选择的字体。我将使用ZCOOL KuaiLe字体进行简体中文翻译,并使用RocknRoll One进行所有其他翻译。

    从以下位置下载字体并将其添加到您的Assets/Fonts文件夹:

  2. We now need to assign some fonts to each language. The current font, Milky Coffee, does not support these four languages. If we try to replace the text with one of these translations, the engine will render any non-supported glyph in a font that does support it but render all supported glyphs (such as punctuation) in the Milky Coffee font. This will result in an inconsistent style that honestly doesn’t look great. For example, as shown in the following figure, the comma and exclamation point are not in the same font as the rest of the text:

    Figure 10.42: The dialogue box with the Korean text rendering in two fonts

    Therefore, we’ll want to change the font whenever the translation occurs to one that we specifically choose. I will use the ZCOOL KuaiLe font for the Simplified Chinese translation and RocknRoll One for all the others.

    Download the fonts from the following locations and add them to your Assets/Fonts folder:

  1. 为每个翻译分配正确的字体。对话框列表中的两个元素应如下所示:
    图 10.43:所有属性均已完成的两个对话框

    图 10.43:所有属性均已完成的两个对话框

    我已经包含了更改字体样式的选项,但在这个例子中,我将把它们全部保留为“正常”

  2. Assign the correct fonts to each of the translations. Your two elements in your Dialogue Boxes list should look as follows:

    Figure 10.43: The two dialogue boxes with all properties completed

    I’ve included the option to change the font style, but I’m going to leave them all at Normal for this example.

  1. 让我们编写代码这将翻译我们的文本。将以下变量声明添加到DialogueSystem.cs脚本的顶部,以便它成为第一个变量声明。这将用于确定我们的游戏在运行时应显示哪种语言。
    [SerializeField] 字符串当前语言;
  2. Let’s write the code that will translate our text. Add the following variable declaration to the top of the DialogueSystem.cs script so that it will be the first variable declaration. This will be used to determine which language our game should display at runtime.
    [SerializeField] string currentLanguage;
  3. 在脚本底部添加以下方法:
    私有无效翻译()
    {
        foreach(DialogueBox dialogBox 在 dialogBoxes 中)
        {
            int 索引 = dialogBox.translations.FindIndex(x => x.languageKey == currentLanguage);
            如果(索引 >= 0)
            {
                dialogueBox.dialogue = dialogueBox.translations[index].translatedString;
                dialogBox.textDisplayBox.font = dialogBox.翻译[index].font;
                dialogBox.textDisplayBox.fontStyle = dialogBox.翻译[index].fontStyle;
            }
        }
    }

    此方法将查找哪些翻译具有currentLanguage变量指定的键。然后它将dialog变量、fontfontStyle更改为适当的值。如果在任何languageKey中都找不到currentLanguage变量,则index将等于-1,并且不会实施任何更改。

  4. Add the following method to the bottom of the script:
    private void Translate()
    {
        foreach (DialogueBox dialogueBox in dialogueBoxes)
        {
            int index = dialogueBox.translations.FindIndex(x => x.languageKey == currentLanguage);
            if (index >= 0)
            {
                dialogueBox.dialogue = dialogueBox.translations[index].translatedString;
                dialogueBox.textDisplayBox.font = dialogueBox.translations[index].font;
                dialogueBox.textDisplayBox.fontStyle = dialogueBox.translations[index].fontStyle;
            }
        }
    }

    This method will find which of the translations have the key designated by the currentLanguage variable. It will then change the dialogue variable, the font, and the fontStyle to the appropriate values. If the currentLanguage variable is not found in any of the languageKeys, index will equal -1 and no changes will be implemented.

  5. 为了确保转换成功,我们需要从Awake()方法中调用该方法。在Start()方法上方添加以下内容
    无效唤醒()
    {
        翻译();
    }
  6. To make sure the translation happens, we need to call the method from the Awake() method. Add the following above your Start() method:
    void Awake()
    {
        Translate();
    }
  7. 返回编辑器然后在当前语言栏输入es。按下播放键时,您会看到对话现在已翻译并更改字体。但是,显示文本时出现问题。您会注意到文本在第二个面板中被截断:
  8. Go back to the Editor and enter es into the Current Language slot. You’ll see that the dialogue now translates and changes font when you press play. However, there is a problem displaying the text. You’ll notice that the text gets cut off in the second Panel:
图 10.44:西班牙语翻译被截断

图 10.44:西班牙语翻译被截断

Figure 10.44: The Spanish translation getting cut off

  1. 回想一下,在翻译文本部分,您不仅应确保您的字体能够呈现文本,还应确保您的文本框有足够的空间容纳翻译后的文本,而翻译后的文本可能会更长(尤其是以不同的字体呈现时)!解决此问题的最简单方法是使用文本组件上的最佳适配设置

    在层次结构中选择两个文本对象,然后从它们的文本组件中选择最佳适合设置

  2. Recall, from the Translating text section, that you should not only make sure that your font will render the text but that your text boxes will have enough room for the translated text, which may be much longer (especially when rendered in a different font)! The easiest way to fix this is to use the Best Fit setting on the Text component.

    Select both Text objects in the Hierarchy and then select the Best Fit setting from their Text components.

  3. 选择Best Fit属性后,将Max Size属性设置为18。这将阻止文本变得太大。
  4. After selecting the Best Fit property, set the Max Size property to 18. This will stop the text from getting too large.

在此示例中,使用最佳适配的一个缺点是字体大小会随着文本动画而变化。这并不理想。但是,为了保持文本框大小,我们目前没有太多选择!在第 12 章中学习滚动矩形和蒙版后,我们可以在滚动时保持字体大小通过文本。

A downside to using Best Fit in this example is the font size changes as the text animates. It’s not ideal. But, to maintain the text box size, we didn’t have much choice – for now! After we learn about Scroll Rects and Masks in Chapter 12, we can maintain the font size while scrolling through the text.

自定义字体

Custom font

让我们远离暂时构建我们的场景,以探索制作自定义字体。我们不会在我们一直在处理的场景中使用这种自定义字体,但创建自定义字体的过程仍然很重要。我们将使用以下精灵创建一个显示数字 0 到 9 的自定义字体:

Let’s take a step away from building out our scenes for a moment to explore making a custom font. We won’t be using this custom font in the scenes we’ve been working on, but the process of creating a custom font is still important to cover. We’ll create a custom font that displays the numbers 0 through 9 using the following sprites:

图 10.45:我们将要创建的自定义字体

图 10.45:我们将要创建的自定义字体

Figure 10.45: The custom font we will create

用于创建的精灵字体是根据https://opengameart.org/content/shooting-gallery上的免费艺术资产修改的

The sprites used to create the font are modified from the free art asset found at https://opengameart.org/content/shooting-gallery.

为了给这个字体创建一个均匀分布的精灵表,我使用了 TexturePacker 程序和 Photoshop。这个过程可以完全用 Photoshop 等照片编辑软件来完成,但是TexturePacker 简化了该过程。TexturePacker 可在https://www.codeandweb.com/texturepacker找到

To create an evenly spaced sprite sheet for this font, I used the TexturePacker program, along with Photoshop. The process can be done entirely with a photo editing software such as Photoshop, but TexturePacker simplifies the process. TexturePacker can be found at https://www.codeandweb.com/texturepacker.

创建自定义字体的过程非常耗时,而且有点麻烦。要创建自定义字体,您必须为计划使用该字体渲染的每个字符输入坐标位置,因此除了数字或非常有限的字符集之外,我不建议使用它。

The process of creating a custom font is time-consuming and kind of a pain. To create a custom font, you have to put in coordinate locations for each character you plan on rendering with the font, so I don’t recommend it for anything other than numbers or a very limited character set.

如果您想要具有更强大字符集的自定义字体,请查看 Unity 资源商店以获取简化位图字体流程的各种选项。

If you want a custom font with a more robust character set, check out the Unity asset store for various options on streamlining the bitmap font process.

要创建上图显示的自定义字体,请完成以下步骤:

To create the custom font displayed in the preceding figure, complete the following steps:

  1. 在Assets/Fonts文件夹中创建一个名为Custom Fonts的新文件夹
  2. Create a new folder called Custom Fonts within your Assets/Fonts folder.
  3. 在代码包中找到customFontSpriteSheet.png文件,并将该文件拖到您在步骤 1中创建的文件夹中。精灵表如下所示:
    图 10.46:自定义精灵表

    图 10.46:自定义精灵表

    手动创建时自定义字体,您的字符必须均匀分布。这将使您在输入单个字符的设置时更加轻松。您可以将精灵表的导入设置保留为默认的精灵(2D 和 UI)纹理类型精灵模式

  4. Find the customFontSpriteSheet.png file within the code bundle and drag the file into the folder you created in Step 1. The sprite sheet appears as follows:

    Figure 10.46: The custom sprite sheet

    When manually creating a custom font, your characters must be spaced evenly. This will make your life significantly easier while entering the settings of the individual characters. You can leave the sprite sheet’s import settings at the defaults of Sprite (2D and UI) Texture Type and Single Sprite Mode.

  1. 自定义字体需要材质才能渲染。右键单击“自定义字体”文件夹并选择“创建” | “材质”,创建新材质。将新材质重命名为“CustomFontMaterial”
  2. A custom font requires a material to render. Create a new material by right-clicking in your Custom Fonts folder and selecting Create | Material. Rename the new material CustomFontMaterial.
  3. 选择CustomFontMaterial以调出其Inspector。将customFontSpriteSheet.png拖到Main MapsAlbedo旁边的方块中。完成后,材质的预览图像应该会更新:
  4. Select CustomFontMaterial to bring up its Inspector. Drag customFontSpriteSheet.png into the square next to Albedo in Main Maps. Once you do so, the material’s preview image should update:
图 10.47:自定义字体材质

图 10.47:自定义字体材质

Figure 10.47: The custom font material

  1. 现在,我们需要改变材质的着色器。着色器有几种不同的选项:您可以使用UI | Default或任何 unlit 或 unlit UI 选项。我更喜欢使用GUI | Text Shader ,因为它渲染正确性最好,而且我更喜欢它在Project视图中的显示方式
  2. Now, we need to change the material’s shader. There are a few different options you can use for the shader: you can use UI | Default or any of the unlit or unlit UI options. My preference is to use GUI | Text Shader as I tend to have the best luck with it rendering correctly, and I prefer the way it displays in the Project view:
图 10.48:自定义字体材质已更新

图 10.48:自定义字体材质已更新

Figure 10.48: The custom font material updated

  1. 现在,我们可以创建自定义字体了。在Custom Fonts文件夹中,右键单击并选择Create | Custom Font。将新字体命名为CustomFont
  2. Now, we can create our custom font. Within the Custom Fonts folder, right-click and select Create | Custom Font. Name the new font CustomFont.
  3. 选择CustomFont并将CustomFontMaterial分配给默认材质槽。
  4. Select CustomFont and assign CustomFontMaterial to the Default Material slot.
  5. Character Rects下的属性将指定精灵表中各个角色的坐标。Size属性指定精灵表中总共有多少个角色。这些属性中的许多属性对于每个角色都是重复的。为了节省时间,我喜欢在将Character Rects大小增加到我希望显示的字符数之前设置Element 0的属性。这样,我在Element 0中指定的属性将复制到所有后续元素。所以,现在将Size属性设置为1。一旦我们拥有了将对所有输入的字符重复的属性,我们就会将其更改为10。当您将Size设置为1时,您应该会看到Element 0的属性出现:
  6. The properties under Character Rects will specify the coordinates of the individual characters on the sprite sheet. The Size property specifies how many total characters are in our sprite sheet. Many of these properties are repeated for each character. To save time, I like to set the properties of Element 0 before I increase Character Rects’s Size to the number of characters I wish to display. This way, the properties I specify in Element 0 will be duplicated to all subsequent Elements. So, set the Size property to 1 for now. We will change it to 10 once we have the properties that will repeat for all characters entered. When you set the Size to 1, you should see the properties of Element 0 appear:
图 10.49:自定义字体的字符矩形

图 10.49:自定义字体的字符矩形

Figure 10.49: The Character Rects of the custom font

  1. 第一个重复的属性所有角色的 UV W和UV H均为UV W和UV H。这些值表示单个角色占精灵总宽度和总高度的百分比。由于我们的角色在精灵表中都是均匀分布的,因此这些值对于所有角色都是相同的。如果您的角色大小均匀,则可以按如下方式计算这些值:
    图 10.50:计算 UV W 和 UV H 值

    图 10.50:计算 UV W 和 UV H 值

    总共有五列字符。因此,每个精灵占据精灵宽度的五分之一。五分之一等于 20% 或小数 0.2。我们需要在UV W插槽中输入0.2。如果你不擅长百分比,那也没关系。Unity 将为您执行计算!在UV W插槽中输入1/5并按Enter将自动计算小数0.2

    总共有两行字符。因此,每个精灵占据精灵高度的一半;一半等于 50% 或小数 0.5。在UV H槽中输入1/2并按Enter键将自动计算出槽中正确的小数0.5 。

  2. The first properties that repeat for all characters are UV W and UV H. These values represent the percentage of the sprite’s total width and total height that the individual character takes up. Since our characters are all evenly spaced in our sprite sheet, these values will be the same for all characters. If your characters are evenly sized, you can calculate these values as follows:

    Figure 10.50: Calculating the UV W and UV H values

    There are a total of five columns of characters. Therefore, each sprite takes up one-fifth of the width of the sprite. One-fifth is equal to 20% or 0.2 as a decimal. We need to put 0.2 in the UV W slot. If you’re not great with percentages, that’s fine. Unity will perform the calculation for you! Typing 1/5 in the UV W slot and pressing Enter will automatically compute the 0.2 decimal.

    There are a total of two rows of characters. Therefore, each sprite takes up one-half of the height of the sprite; one-half is equal to 50% or 0.5 as a decimal. Typing 1/2 in the UV H slot and pressing Enter will automatically compute the correct decimal of 0.5 in the slot.

  1. 下一个重复的值每个角色都有Vert WVert H值。创建精灵表时,每个精灵都是由 50x57 像素的图像组成的。因此,在W槽中输入50,在H槽中输入-57。请记住,H值始终为负数!
  2. The next values that repeat for each character are the Vert W and Vert H values. When the sprite sheet was created, each individual sprite was made from a 50x57 pixel image. So, type 50 in the W slot and -57 in the H slot. Remember that the H value will always be negative!
  3. 最后一个在所有角色中保持一致的属性是Advance属性。此属性是角色之间的空间。由于我们的精灵宽度为 50,因此我们应该将此属性设置为 50 或更大。您可以摆弄此属性以查看最适合您的效果,但我认为51看起来不错

    完成步骤 911后,你的元素 0角色应具有以下属性:

  4. The last property that remains consistent throughout all the characters is the Advance property. This property is the space between the characters. Since our sprites have a width of 50, we should make this property 50 or larger. You can fiddle with this property to see what looks best to you, but I think it looks nice at 51.

    After completing Steps 9 through 11, your Element 0 character should have the following properties:

图 10.51:元素 0 及其值已填入

图 10.51:元素 0 及其值已填入

Figure 10.51: Element 0 with its values filled in

  1. 现在我们已经所有重复的属性,我们可以增加Character Rects集的大小。我们的精灵表中总共有 10 个角色,因此将Size属性更改为10。您会看到,一旦我们这样做,元素 0的属性就会在元素 1元素 9中重复出现
  2. Now that we have placed all the repeated properties, we can increase the Size of our Character Rects set. There are a total of 10 characters in our sprite sheet, so change the Size property to 10. You’ll see that once we do this, the properties of Element 0 are repeated in Element 1 through Element 9:
图 10.52:元素 0 至元素 10 的重复

图 10.52:元素 0 至元素 10 的重复

Figure 10.52: Element 0 duplicated through Element 10

  1. 现在,我们需要指定每个元素的 ASCII 索引。这会将输入的文本与正确的精灵联系起来。如果没有这个,字体就不会知道输入数字 0 应该会显示表单中的第一个精灵。下表表明数字 0 到 9 是 ASCII 数字 48 到57: http: //www.ascii.cl/

    因此,我们应该进入元素 0元素 9中的索引4857

    元素

    0

    1

    2

    3

    4

    5

    6

    7

    8

    9

    指数

    四十八

    49

    50

    51

    52

    53

    54

    55

    56

    57

  2. Now, we need to specify the ASCII index of each element. This will tie the typed text to the correct sprite. Without this, the font wouldn’t know that typing the number 0 should show the first sprite in the sheet. The following table indicates that the numbers 0 to 9 are the ASCII numbers 48 to 57: http://www.ascii.cl/.

    Therefore, we should enter the Index values of 48 through 57 in Element 0 through Element 9:

    Element

    0

    1

    2

    3

    4

    5

    6

    7

    8

    9

    Index

    48

    49

    50

    51

    52

    53

    54

    55

    56

    57

表10.6:各元素的索引值

Table 10.6: The index values of each element

  1. 下一步是指定UV XY值。这是创建自定义字体中最耗时的部分。这些值表示角色在精灵表中的 UV 坐标位置。这些数字是根据角色所在的行号和列号计算得出的。行号和列号从 0 开始,从左下角开始
    图 10.53:精灵表的行和列

    图 10.53:精灵表的行和列

    要计算UV XUV Y值,请使用以下公式:

    图 10.54:计算 UV X 和 UV Y

    图 10.54:计算 UV X 和 UV Y

    请记住,由于我们的角色是均匀分布的,我们的UV WUV H值对于每个角色都是相同的

    因此,如果我们查看元素 0,则可以通过将 0 (代表第 0 列)乘以 0.2 (UV W值)得到0来找到其UV X值。可以通过将 1 (代表第 1 行)乘以 0.5 (UV H 值)得到0.5来找到其UV Y值

    下表表示应为每个字符输入的UV XUV Y值

    元素

    紫外线

    紫外线

    0

    0

    0.5

    1

    0.2

    0.5

    2

    0.4

    0.5

    3

    0.6

    0.5

    4

    0.8

    0.5

    5

    0

    0

    6

    0.2

    0

    7

    0.4

    0

    8

    0.6

    0

    9

    0.8

    0

    表 10.7:字体的 UV X 和 UV Y 属性

    下图更直观地表示了坐标模式:

    图 10.55:精灵表的坐标

    图 10.55:精灵表的坐标

  2. The next step is to specify the UV X and Y values. This is the part of creating custom fonts that takes up the most time. These values represent the UV coordinate position of the characters in the sprite sheet. These numbers are calculated based on the row and column numbers that the character lies in. The row and column numbers start at 0 and start in the bottom-left corner:

    Figure 10.53: The rows and columns of the sprite sheet

    To calculate the UV X and UV Y values, use the following formulas:

    Figure 10.54: Calculating UV X and UV Y

    Remember that since our characters are evenly spaced, our UV W and UV H values are the same for each character.

    So, if we look at Element 0, its UV X value can be found by multiplying 0 (for column 0) by 0.2 (the UV W value) to get 0. Its UV Y value can be found by multiplying 1 (for row 1) by 0.5 (the UV H value) to get 0.5.

    The following chart represents the UV X and UV Y values that should be entered for each character:

    Element

    UV X

    UV Y

    0

    0

    0.5

    1

    0.2

    0.5

    2

    0.4

    0.5

    3

    0.6

    0.5

    4

    0.8

    0.5

    5

    0

    0

    6

    0.2

    0

    7

    0.4

    0

    8

    0.6

    0

    9

    0.8

    0

    Table 10.7: The UV X and UV Y properties of the font

    The following figure provides a more visual representation of the coordinate pattern:

    Figure 10.55: The coordinates of the sprite sheet

  1. 现在我们可以测试我们的字体看看它是否有效。在任何场景中,创建一个名为Custom Font Text 的新UI Text对象。我创建了一个名为Chapter10-Examples-CustomFont 的新场景,其中有我的测试字体。将Text更改为0123456789并将CustomFont拖到Font插槽中。您应该看到类似以下内容:
  2. Now, we can test our font to see whether it works. Within any of your scenes, create a new UI Text object named Custom Font Text. I have created a new scene called Chapter10-Examples-CustomFont on which I have my test fonts. Change the Text to 0123456789 and drag the CustomFont into the Font slot. You should see something like the following:
图 10.56:步骤 15 后应显示的自定义字体

图 10.56:步骤 15 后应显示的自定义字体

Figure 10.56: The custom font as it should be displayed after Step 15

  1. 为了使其正确显示,请扩大文本框的大小以容纳所有字符,并将文本颜色更改为白色:
  2. To make it display correctly, expand the size of the text box to accommodate all the characters and change the text color to white:
图 10.57:调整一些属性后的自定义字体

图 10.57:调整一些属性后的自定义字体

Figure 10.57: The custom font after adjusting some properties

现在我们已经完成了导入我们的自定义字体,让我们看看进一步调整它。

Now that we’ve completed importing our custom font, let’s look at adjusting it further.

一个调整字符间距和更改字体大小

Adjusting the character spacing and changing the font size

在我们的例子中,所有数字间距均匀字体大小无法可以更改。您可能希望数字更接近或字体大小不同。让我们更改自定义字体,使精灵更接近。下图显示了调整后的字体与我们在本示例的最后一部分中创建的原始字体的对比:

In our example, all the numbers are evenly spaced and the font size cannot be changed. You will likely want your numbers to be closer together or of a different font size. Let’s alter our custom font so that the sprites are closer together. The following figure shows our font after the adjustments versus the original we created in the last part of this example:

图 10.58:调整间距后的自定义字体和不调整间距后的自定义字体

图 10.58:调整间距后的自定义字体和不调整间距后的自定义字体

Figure 10.58: The custom font with and without spacing adjustment

要更改字符间距,请完成以下步骤:

To change the spacing of the characters, complete the following steps:

  1. 使用Ctrl + D复制CustomFont并将复制的CustomFontTight重命名。
  2. Duplicate the CustomFont with Ctrl + D and rename the duplicate CustomFontTight.
  3. 打开CustomFontTight检查器
  4. Open the Inspector of CustomFontTight.
  5. 如果您希望数字更接近,只需更改特定元素的Advance属性即可。 Advance属性表示从精灵的开始到下一个精灵的开始的像素。因此,如果我们希望数字 2 看起来更接近数字 1,我们可以将元素 1Advance属性更改为更小的值,如下所示:
  6. If you’d like the numbers to be closer together, you simply have to change the Advance property for the specific elements. The Advance property represents the pixels from the start of the sprite to the start of the next sprite. So, if we wanted the number 2 to appear closer to the number 1, we could change the Advance property of Element 1 to something smaller, as shown here:
图 10.59: 1 个字形间距问题

图 10.59: 1 个字形间距问题

Figure 10.59: The 1 glyph spacing problem

笔记

Note

您可以在预览场景中的结果时更改自定义字体的设置。要使更改自定义字体后文本在场景中刷新,您必须先保存场景。

You can change the settings on your custom font while previewing the results in the scene. To get the text to refresh in the scene after making a change to your custom font, you have to save the scene first.

  1. 调整Advance属性基于每个元素 下列的图表:

    元素

    0

    1

    2

    3

    4

    5

    6

    7

    8

    9

    进步

    三十八

    三十

    三十五

    三十五

    三十五

    三十五

    三十五

    三十五

    三十八

    三十五

  2. Adjust the Advance properties of each element based on the following chart:

    Element

    0

    1

    2

    3

    4

    5

    6

    7

    8

    9

    Advance

    38

    30

    35

    35

    35

    35

    35

    35

    38

    35

表 10.8:自定义字体的高级属性

Table 10.8: Advance properties of the custom font

  1. 字体现在应如下所示:
  2. The font should now appear as follows:
图 10.60:显示字体的所有字形

图 10.60:显示字体的所有字形

Figure 10.60: All the glyphs of the font displayed

  1. 0 和 1 之间仍然有太多的间距;但是,如果您尝试减少0 上的Advance属性以使 1 更近,则其他数字如果跟在 0 后面,就会与 0 重叠。以下示例显示了如果将元素 0的Advance属性减小到 35会发生什么情况;1 跟在 0 后面看起来很好,但 9与其重叠:
  2. There is still a bit too much spacing between the 0 and the 1; however, if you try to reduce the Advance property on the 0 to bring the 1 closer, other numbers will overlap the 0 if they follow the 0. The following example shows what will happen if the Advance property of Element 0 were reduced to 35; the 1 looks good following 0, but the 9 overlaps it:
图 10.61:0 字形上的不同高级设置

图 10.61:0 字形上的不同高级设置

Figure 10.61: Different Advance settings on the 0 glyph

  1. 考虑到这一点,我们需要在我们的场景中将 1 拉近一点。要将 1 拉近一点,我们需要更改元素 1上的Vert X属性。将元素 1上的Vert X属性更改为-3,使其稍微向左移动一点。这将为角色提供更有利的间距。
  2. Keeping that in mind, we need to bring 1 closer in our scenario. To move the 1 closer, we need to change the Vert X property on Element 1. Change the Vert X property on Element 1 to -3 to shift it left just a smidge. This will give the character a more favorable spacing.

现在,如果您想调整字体大小,则无法通过更改Text 组件的Font Size属性来更改自定义字体的大小;更改Font Size不会产生任何效果。要更改字体大小,必须更改Rect Transform 组件的Scale XScale Y属性。要使字体大小减半,请将Scale XScale Y更改 0.5

Now, if you’d like to adjust the font size, you cannot change the size of a custom font by changing the Font Size property of the Text component; changing the Font Size will do nothing. To change the size of the font, you must change the Scale X and Scale Y properties of the Rect Transform component. To get the font half the size, change Scale X and Scale Y to 0.5.

正如我提到的早些时候,我们可能不会使用要构建的字体我们的主要示例场景,但创建自定义字体的过程仍然是一个有用的练习。现在,让我们继续创建一些其他常见的 UI 资产——健康条和进度条

As I mentioned earlier, we probably won’t be using this font to build our master example scene, but the process of creating a custom font is still a useful exercise. Now, let’s move on to creating some other common UI assets – health bars and progress bars.

TextMeshPro - 带渐变的扭曲文本

TextMeshPro - Warped Text with Gradient

我们的暂停面板的横幅看起来目前有点空。现在我们已经介绍了如何使用 TextMeshPro - Text,我们可以创建一个漂亮的曲线文本,带有渐变,与横幅很好地对齐:

The banner of our Pause Panel looks a bit bare currently. Now that we’ve covered using TextMeshPro - Text, we can create a nice curved text with a gradient that lines up well with our banner:

图 10.62:带有渐变和扭曲的横幅文本

图 10.62:带有渐变和扭曲的横幅文本

Figure 10.62: The banner text with a gradient and warp

创建如图所示的弯曲文本在上面的屏幕截图中,完成以下步骤:

To create the curved text shown in the preceding screenshot, complete the following steps:

  1. 复制第 9 章-示例场景并将新场景命名为第 10 章-示例
  2. Duplicate the Chapter 9-Examples scene and name the new scene Chapter 10-Examples.
  3. 选择暂停横幅UI 图像,然后在层次结构中右键单击它并选择UI | Text - TextMeshPro ,为其添加一个子文本 (TMP) 对象。将子对象重命名为Paused TMP
  4. Select the Pause Banner UI Image and give it a child Text (TMP) object by right-clicking on it in the Hierarchy and selecting UI | Text - TextMeshPro. Rename the child object Paused TMP.
  5. 如果您还没有这样做,请在弹出窗口提示您这样做时选择导入 TMP Essentials导入 TMP 示例和附加内容。如果您之前只导入了 TMP Essentials,但没有导入示例,请转到文件 | 项目设置| TextMeshPro并选择导入 TMP 示例和附加内容。
  6. If you have not done so already, select Import TMP Essentials and Import TMP Examples & Extras when the popup prompts you to do so. If you previously only imported TMP Essentials, but not the examples, go to File | Project Settings | TextMeshPro and select Import TMP Examples & Extras.
  7. 在TextMeshPro - Text (UI)组件中,将文本输入框设置更改为单词Paused
  8. In the TextMeshPro - Text (UI) component, change the Text Input Box setting to the word Paused.
  9. 将字体样式 设置粗体
  10. Set the Font Style to Bold.
  11. 将文本水平和垂直居中对齐。
  12. Center-align the text horizontally and vertically.
  13. 将字体大小 设置43
  14. Set the Font Size to 43.
  15. 将字体更改为Roboto-Bold。(请注意,仅当您导入 TMP 示例时,此字体资产才可用。)
  16. Change the font to Roboto-Bold. (Note this Font Asset will only be available if you imported the TMP Examples.)
  17. 调整 Rect Transform,使文本在横幅内更居中。您的文本应显示如下:
  18. Adjust the Rect Transform so that the text is centered more within the banner. Your text should appear as follows:
图 10.63:横幅文本的放置

图 10.63:横幅文本的放置

Figure 10.63: The banner text’s placement

  1. 现在,让我们给出文本渐变填充。选中TextMeshPro - Text ( UI)组件中Color Gradient旁边的复选框
  2. Now, let’s give the text a gradient fill. Select the checkbox next to Color Gradient in the TextMeshPro - Text (UI) component.
  3. 为了达到理想的效果,我们将左上角和右上角的颜色保留为白色。选择左下角的白色矩形以调出颜色选择器。选择颜色选择器顶部的滴管,然后将鼠标移到暂停面板图像的棕褐色区域上。关闭颜色选择器窗口时,棕褐色将位于左下角的插槽中。
  4. To achieve the desired look, we will leave the top-left and top-right colors white. Select the white rectangle at the bottom left to bring up the Color picker. Select the eye dropper at the top of the Color picker and then move your mouse over the tan area of the Pause Panel image. When you close the Color picker window, the tan color will be in the bottom-left slot.
图 10.64:使用滴管工具获取文本颜色

图 10.64:使用滴管工具获取文本颜色

Figure 10.64: Using the eye dropper tool to get the text color

  1. 右键单击颜色选择复制
  2. Right-click on the color in the bottom-left slot and select Copy.
  3. 右键点击右下角的白色方块并选择粘贴。现在,顶部的两种颜色应该是白色,底部的两种颜色应该是棕褐色。
  4. Right-click on the white square in the bottom-right slot and select Paste. Now, the two top colors should be white and the two bottom colors should be tan.

图 10.65:颜色渐变属性完成

图 10.65:颜色渐变属性完成

Figure 10.65: The Color Gradient properties completed

  1. 在Outline设置中(在底部,TextMeshPro - Text (UI)组件的外面),选中Outline一词旁边的复选框以启用轮廓。将Color设置为白色,并将Thickness更改 0.25
  2. In the Outline settings (at the bottom, outside of the TextMeshPro - Text (UI) component), select the checkbox next to the word Outline to enable the outline. Set the Color to white and change the Thickness to 0.25.
  3. 最后要做的是弯曲文本。TextMeshPro 通过提供示例让我们可以轻松完成此操作在运行时弯曲文本的脚本。要查看更改,您必须玩游戏,并且它们不会在场景视图中显示。选择添加组件按钮并选择脚本| TMPro.Examples | Warp Text Example
  4. The last thing to do is curve the text. TextMeshPro has made this pretty easy for us by providing an example script that curves text at runtime. To view the changes, you have to play the game, and they are not represented in the Scene view. Select the Add Component button and choose Scripts | TMPro.Examples | Warp Text Example.
图 10.66:Warp Text 示例组件

图 10.66:Warp Text 示例组件

Figure 10.66: The Warp Text Example component

  1. 选择Vertex Curve槽中的波浪绿线以调出曲线编辑器。选择最左边的选项,即平线。
  2. Select the wavy green line in the Vertex Curve slot to bring up the curve editor. Select the option on the far left – that is, the flat line.
图 10.67:为顶点曲线选择一条平坦曲线

图 10.67:为顶点曲线选择一条平坦曲线

Figure 10.67: Selecting a flat curve for the Vertex Curve

  1. 右键单击绿色线在0.5标记处并选择添加键
  2. Right-click on the green line at the 0.5 mark and select Add Key.
  3. 选择该新键并将其向上拖动至1.3下方
  4. Select that new key and drag it upward to a little below 1.3.
图 10.68:顶点曲线的最终版本

图 10.68:顶点曲线的最终版本

Figure 10.68: The final version of the Vertex Curve

  1. 从此场景开始玩游戏并按P键查看暂停面板。文本现在应像本示例开头那样显示
  2. Play the game from this scene and press the P key to view the Pause Panel. The text should now appear as it did at the beginning of this example.
  3. 要查看整个流程、开始屏幕、介绍场景以及更新后的暂停面板的场景,我们需要更新一些旧场景。打开Chapter10-Examples-IntroScene并将文本画布对话系统组件上的下一个场景Chapter9-Examples更改Chapter10-Examples
  4. To view the entire flow, Start Screen, Intro Scene, and this scene with the updated Pause Panel, we’ll need to update some old scenes. Open Chapter10-Examples-IntroScene and change the Next Scene on the Text CanvasDialogue System component from Chapter9-Examples to Chapter10-Examples.
  5. 复制名为Chapter9-Examples-StartScreen的场景并将其命名为Chapter10-Examples-StartScreen
  6. Duplicate the scene called Chapter9-Examples-StartScreen and name it Chapter10-Examples-StartScreen.
  7. 打开新场景并选择Button CanvasPlay Button子项
  8. Open the new scene and select the Play Button child of the Button Canvas.
  9. 将Level Loader组件上的要加载的场景更改Chapter10-Examples-IntroScene
  10. Change the Scene To Load on the Level Loader component to Chapter10-Examples-IntroScene.
  11. 打开Build Settings并将Build 中的场景更新为以下内容:
    图 10.69:包含所有适当场景的构建设置

    图 10.69:包含所有适当场景的构建设置

    您可以通过右键单击并删除不需要的场景来删除它们。

  12. Open the Build Settings and update the Scenes in Build to the following:

    Figure 10.69: The Build Settings with all the appropriate scenes

    You can remove unnecessary scenes by right-clicking on them and removing them.

  1. 打开Chapter10-Examples-StartScreen场景后,按下编辑器中的播放按钮并观看游戏的整个流程。
  2. With the Chapter10-Examples-StartScreen scene open, press the play button in the Editor and watch the game’s flow complete.

笔记

Note

我想指出的是,虽然我在每个新章节中复制和重命名每个场景,但您不必这样做。我这样做是为了维护一个易于查看的进度日志,以了解本书场景的进展情况,但不可否认的是,如果您也这样做,您的项目可能会变得有点混乱。所以,如果你想知道“为什么我不能一直添加场景,而不是每次都开始一个新场景?”你可以!

I’d like to point out that, while I am duplicating and renaming each scene with each new chapter, you do not have to do so. I am doing it to maintain an easy-to-view progression log of what is happening to our scenes for this book, but admittedly, your project is probably getting a bit cluttered if you’ve been doing it, too. So, if you are wondering “Why can’t I just keep adding to my scenes instead of starting a new one each time?” you can!

这就是创建使用 TextMeshPro包裹文本!

And that’s it for creating a wrapped text using TextMeshPro!

概括

Summary

哇!我敢打赌,当本章开始时,你一定想过,关于文本,你到底能说多少?没想到这是迄今为止最长的一章!我差点通过添加更多示例使其更长!唉,尽管我想提供更多示例,但我必须遵守页数限制——尽管我已经超过了这个限制。如果您渴望获得有关 UI Text 和 Text-TextMeshPro 的更多示例,我强烈建议您查看本章中链接的各种示例,以及您使用 TextMesh – Pro 示例下载的示例场景。

Wow! I bet you thought, how much can you really say about text? when this chapter started and didn’t expect to be faced with the longest chapter so far! And I almost made it longer by adding more examples! Alas, as much as I would like to provide even more examples, I have a page limit I have to adhere to – even though I have already blown past it. If you are hankering for even more examples concerning UI Text and Text-TextMeshPro, I strongly recommend you review the various examples linked within this chapter, as well as the example scenes that you downloaded with the TextMesh – Pro examples.

在下一章中,我们将深入研究 UI 图像和效果。

In the next chapter, we’ll do a deep dive into UI Images and Effects.

11

11

UI 图像和效果

UI Images and Effects

我们在前面的章节中已经使用了 UI 图像,但现在我们将进一步了解组件的特定属性,以及如何通过代码访问组件。我们还将介绍一些可以应用于 UI 对象以增加视觉吸引力的 UI 效果组件。虽然我们将彻底研究这些组件,但本章的大部分内容将重点介绍视频游戏(尤其是移动视频游戏)中 UI 功能的具体示例。

We’ve worked with UI Images in the previous chapters, but now we’ll learn more about the component’s specific properties, as well as how to access the component via code. We’ll also look at some of the UI effect components that we can apply to our UI objects for visual appeal. While we will look at the components thoroughly, the majority of this chapter focuses on specific worked-out examples of UI functionality that you will find in video games, particularly mobile video games.

在本章中,我们将讨论以下主题:

In this chapter, we will discuss the following topics:

  • 创建 UI 图像并设置其属性
  • Creating UI Images and setting their properties
  • 使用各种 UI 效果组件进一步定制我们的图形 UI
  • Using the various UI effects components to further customize our graphical UI
  • 实现水平和圆形进度条
  • Implementing horizontal and circular progress bars
  • 如何创建不使用内置过渡效果的按钮来交换精灵,例如静音/取消静音按钮
  • How to create Buttons that swap sprites without using the built-in transitions, like a mute/unmute Button
  • 添加按住/长按功能
  • Adding a press-and-hold/long-press functionality
  • 创建屏幕上的四向虚拟方向键
  • Creating an onscreen four-directional virtual D-Pad
  • 创建浮动的八向虚拟模拟摇杆
  • Creating a floating eight-directional virtual analog stick

笔记

Note

示例部分之前的部分中显示的所有示例都可以在代码包中提供的 Unity 项目中找到。它们可以在标记为Chapter11 的场景中找到

All the examples shown in the sections before the Examples section can be found within the Unity project provided in the code bundle. They can be found within the scene labeled Chapter11.

每个示例图都有一个标题,说明场景中的示例名称。

Each example figure has a caption stating the example name within the scene.

在场景中,每个示例都在其自己的画布上,并且一些画布处于停用状态。要查看停用画布上的示例,只需在检查器中选中画布名称旁边的复选框即可。每个画布还具有自己的事件系统。如果您一次激活多个画布,这将导致错误

In the scene, each example is on its own Canvas, and some of the Canvases are deactivated. To view an example on a deactivated Canvas, simply select the checkbox next to the Canvas’ name in the Inspector. Each Canvas is also given its own Event System. This will cause errors if you have more than one Canvas activated at a time.

技术要求

Technical requirements

您可以在此处找到本章节的相关代码和资产文件https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2011

You can find the relevant codes and asset files of this chapter here: https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2011

UI 图像组件属性

UI Image component properties

我们之前已经创建过 UI 图像,但是让我们看看它的属性和组成部分。

We’ve created a UI Image before, but let’s look at its properties and components.

您可以使用+ | UI | Image创建一个新的 UI Image 对象

You can create a new UI Image object using + | UI | Image.

UI Image对象包含Rect TransformCanvas Renderer组件以及Image组件。我们已经详细了解了Rect TransformCanvas Renderer组件;现在,让我们看看Image组件。

The UI Image object contains the Rect Transform and Canvas Renderer components as well as the Image component. We’ve looked at the Rect Transform and Canvas Renderer components extensively; now, let’s look at the Image component.

Image组件上的第一个设置是Source Image属性,它表示将要渲染的精灵。Color属性表示要渲染的精灵的基本颜色。将颜色保留为白色将使图像看起来与精灵完全一样,但更改颜色将为图像添加有色叠加。您还可以通过降低 alpha 值来更改图像的透明度。Material 属性允许您向图像添加材质。

The first setting on the Image component is the Source Image property, which represents the sprite that will be rendered. The Color property represents the base color of the sprite being rendered. Leaving the color at white will make the Image appear exactly as the sprite, but changing the color will add a tinted color overlay to the Image. You can also change the transparency of the Image by reducing the alpha value. The Material property allows you to add a material to the Image.

Raycast TargetRaycast Padding属性的工作方式与Text组件相同,通过指定图像是否会阻止点击其后面的 UI 对象以及是否有任何填充来阻止点击。Maskable属性确定图像是否会受到遮罩的影响

The Raycast Target and Raycast Padding properties work the same way they do on the Text component, by specifying whether the Image will block clicks on UI objects behind it or not and if there is any padding to the block. The Maskable property determines if the Image can be affected by masks or not.

当将精灵分配到“源图像”插槽后, “图像类型”下的图像组件中会出现新的选项,如下图所示

When a sprite is assigned to the Source Image slot, new options appear in the Image component under Image Type, as shown in the following figure:

图 11.1:UI 图像组件及其所有属性

图 11.1:UI 图像组件及其所有属性

Figure 11.1: The UI Image component and all its properties

让我们看看各种图像类型的选项以及它们如何影响精灵。

Let’s look at the various options for Image Type and how they affect a sprite.

图像类型

Image Type

图像类型属性​​决定Source Image指定的精灵会以何种方式呈现。一共有四个选项:SimpleSlicedTiledFilled。我们来看看它们。

The Image Type property determines how the sprite specified by Source Image will appear. There are four options: Simple, Sliced, Tiled, and Filled. Let’s take a look at them.

简单的

Simple

图像类型属性​​设置为“简单”的图像会在整个精灵中均匀缩放。这是默认类型。选择“简单”时作为图像类型,标记为使用精灵网格的切换开关,标记为保留纵横比的切换开关和标记为设置原生大小的 按钮变为可用。

An Image with its Image Type property set to Simple scales evenly across the sprite. This is the default type. When Simple is selected as the Image Type, a toggle labeled Use Sprite Mesh, a toggle labeled Preserve Aspect, and a button labeled Set Native Size become available.

选择使用 Sprite Mesh切换按钮将使图像使用TextureImporter创建的 Sprite Mesh。默认情况下,此属性处于取消选择状态,并且 Sprite 的网格是四边形。如果您不希望图像由矩形表示,而是希望它具有紧密贴合图像可见区域的网格,则需要选择此属性。

Selecting the Use Sprite Mesh toggle will have the Image use the sprite mesh created by the TextureImporter. By default, this property is deselected, and the sprite’s mesh is a quad. You will select this property if you don’t want the Image represented by a rectangle, but instead want it to have a mesh that fits tightly around the visible area of the Image.

勾选“保留纵横比”属性后,精灵将保留其部分显示,可能不会填满矩形变换的整个区域。选择此属性可确保您的精灵看起来与原先预期一致,不会拉伸。

When the Preserve Aspect property is checked, the sprite will display with its portions preserved and may not appear to fill the entire area of the Rect Transform. Selecting this property ensures that your sprites look as originally intended and are not stretched out.

选择“设置原始尺寸”按钮将图像的尺寸设置为spri的像素尺寸德。

Selecting the Set Native Size button sets the dimensions of the Image to the pixel dimensions of the sprite.

切片

Sliced

切片图像被分成九个区域。当切片图像被缩放时,图像的所有区域都会被缩放,除了角落。这允许您缩放图像而不扭曲其角。此功能特别适用于具有圆角的精灵,您希望能够将其拉伸为圆角矩形。

Sliced Images are split into nine areas. When a Sliced Image is scaled, all areas of the Image are scaled, except the corners. This allows you to scale an Image without distorting its corners. This works particularly well with sprites that have rounded corners that you want to be able to stretch into rounded rectangles.

当图像设置为切片时,将出现填充中心每单位像素乘数属性。下图显示了一个圆角矩形,其中有五个替代版本正在拉伸。您可以看到,选择切片如何允许圆角矩形以保持圆角矩形形状的方式拉伸,而将图像类型保留为简单会导致图像在缩放时边缘变形。

When an image is set to Sliced, the Fill Center and Pixels Per Unit Multiplier properties appear. The following figure shows a rounded rectangle with five alternate versions of it being stretched. You can see how selecting Sliced allows the rounded rectangle to stretch in a way that maintains the rounded rectangle shape while leaving the Image Type at Simple causes a distortion in the edges of the Image when it is scaled.

图 11.2:第 11 章场景中的切片图像类型示例

图 11.2:第 11 章场景中的切片图像类型示例

Figure 11.2: Sliced Image Type Example in the Chapter11 scene

您必须在 Sprite 或 Sprite Sheet 的Sprite Editor中指定这九个区域的位置。如果您尚未指定区域,则图像组件中将显示一条消息。

You must specify where the nine areas will be, within the Sprite Editor of the sprite or sprite sheet. If you have not specified the regions, a message will appear within the Image component.

要在Sprite 编辑器中指定区域,您需要将精灵边缘的绿色框拖到所需位置。从以下屏幕截图中可以看到,您需要将绿线,这样它们就不会围绕边缘的曲线了:

To specify the area in the Sprite Editor, you need to drag the green boxes on the edges of the sprite to the desired position. As you can see from the following screenshot, you want to drag the green lines so that they stop surrounding the curves of the edges:

图 11.3:在 Sprite 编辑器中指定精灵的九个区域

图 11.3:在 Sprite Edi 中指定精灵的九个区域托尔

Figure 11.3: Specifying the nine areas of a sprite in the Sprite Editor

接下来我们来谈谈Image Type下的Tiled选项

Next, let’s talk about the Tiled option under the Image Type.

平铺

Tiled

选择“平铺”作为图像类型将导致要重复的图像以填充拉伸区域。下图演示了选择“简单”“平铺”作为“图像类型”如何影响缩放的图像:

Selecting Tiled for Image Type will cause the Image to repeat to fill the stretched area. The following figure demonstrates how selecting Simple and Tiled for Image Type affects the scaled Images:

图 11.4:第 11 章场景中的平铺图像类型示例

图 11.4:第 11 章场景中的平铺图像类型示例

Figure 11.4: Tiled Image Type Example in the Chapter11 scene

接下来我们来谈谈填充选项在图像 T输入

Next, let’s talk about the Filled option under the Image Type.

已填满

Filled

图像类型选择为填充的图像将填充精灵的百分比,从指定方向的原点开始。精灵中超过指定百分比的任何部分都不会被渲染。选择“填充”后,将显示新属性:

Images with Filled selected for their Image Type will fill in a percentage of the sprite, starting at an origin in a specified direction. Any part of the sprite past the designated percentage will not be rendered. When Filled is selected, new properties are displayed:

图 11.5:填充图像的属性

图 11.5:填充图像的属性

Figure 11.5: Properties for a Filled Image

填充方法属性决定精灵是水平、垂直还是径向填充。有五个选项:水平垂直径向 90径向 180径向 360。每个填充方法选项都将从填充原点开始绘制精灵,直到填充量。当选择其中一种径向方法时,您还可以选择让填充按顺时针方向进行;如果您选择不选择此选项,则图像将按逆时针方向填充。下图演示了三种填充方法选项,所有填充量值均为0.75%75%:

The Fill Method property determines whether the sprite will be filled horizontally, vertically, or radially. There are five options: Horizontal, Vertical, Radial 90, Radial 180, and Radial 360. Each of these Fill Method options will begin drawing the sprite at the Fill Origin up to the Fill Amount. When one of the radial methods is selected, you can also select the option to have the fill progress Clockwise; if you choose not to select this option, the Image will fill counterclockwise. The following figure demonstrates the three Fill Method options, all with Fill Amount values of 0.75 or 75 percent:

图 11.6:第 11 章场景中的填充图像类型示例

图 11.6:第 11 章场景中的填充图像类型示例

Figure 11.6: Filled Image Type Example in the Chapter11 scene

当您看到水平垂直 填充方法选项的实际作用时,它们的含义就不言自明了,但仅从外观上判断这三种径向方法的工作原理则有些困难。径向 90将径向中心置于一个角上,径向 180将径向中心置于一个边上,径向 360将径向中心置于精灵的中心。

The Horizontal and Vertical Fill Method options are somewhat self-explanatory when you see them in action, but it’s a little more difficult to determine exactly how the three radial methods work just from looking at them. Radial 90 places the center of the radial at one of the corners, Radial 180 places the center of the radial at one of the edges, and Radial 360 places the center of the radial in the center of the sprite.

填充 图像类型选项还具有设置原始大小属性。

The Filled Image Type option also has the Set Native Size property.

现在我们已经探索了 UI 图像组件,我们可以看看一些UI效果组件科目。

Now that we’ve explored the UI Image component, we can look at some UI effect components.

UI 效果组件

UI effect components

三种效果组件可让您添加特殊效果添加到文本和图像对象中:阴影轮廓位置作为 UV1 。它们都可以在添加组件| UI |效果下找到。让我们分别看一下每一个,从阴影合成开始愛爾蘭。

Three effects components allow you to add special effects to your Text and Image objects: Shadow, Outline, and Position as UV1. They can all be found under Add Component | UI | Effects. Let’s look at each one individually, starting with the Shadow component.

阴影

Shadow

阴影组件添加了为您的文本或图像对象添加简单的阴影。

The Shadow component adds a simple shadow to your Text or Image object.

图 11.7:阴影组件

图 11.7:阴影组件

Figure 11.7: The Shadow component

您可以使用“效果颜色”属性更改阴影的颜色和透明度。“效果距离”属性确定其相对于其所附加图形的位置。“使用图形 Alpha”属性将阴影的颜色与其所附加图形的颜色相乘。因此,如果选中此属性并降低原始图形的 Alpha(不透明度),则阴影的 Alpha 也会降低,结果阴影是两个 Alpha 值的乘积。但是,如果未选中“使用图形 Alpha”属性,则无论原始图形的 Alpha 如何,阴影都将保持其 Alpha 值。因此,如果您将原始图形的 Alpha 一直调低到0 ,使其不可见,则阴影将根据“效果颜色”属性上指定的 Alpha 保持可见

You can change the color and transparency of the shadow with the Effect Color property. The Effect Distance property determines its position relative to the graphic to which it is attached. The Use Graphic Alpha property will multiply the color of the shadow with the color of the graphic on which the shadow is attached. So, if this property is checked and the alpha (opacity) of the original graphic is reduced, the alpha of the shadow will reduce as well, with the resulting shadow being a product of the two alpha values. However, if the Use Graphic Alpha property is unchecked, the shadow will maintain its alpha value regardless of the alpha of the original graphic. So, if you turned the alpha of the original graphics all the way down to 0, rendering it invisible, the shadow would remain visible based on the alpha specified on the Effect Color property.

下图显示了Shadow组件运行的一些示例:

The following figure shows a few examples of the Shadow component in action:

图 11.8:第 11 章场景中的阴影组件示例

图 11.8:第 11 章场景中的阴影组件示例

Figure 11.8: Shadow Component Example in the Chapter11 scene

所有四根香蕉的阴影组件的“效果颜色”属性都设置了相同的 alpha 值。第一根香蕉的图像组件的颜色属性将 alpha 设置为完全不透明。第二根、第三根和第四根香蕉的图像组件的不透明度降低。第二根和第三根香蕉具有相同的属性,但第二根香蕉第一个使用了“使用图形 Alpha”属性,而第三个没有。因此,你可以看到,第三个香蕉的阴影并没有因为香蕉图像组件的变暗而变暗。第四个也是最后一个香蕉的图像组件的不透明度设置为0,但由于阴影组件上没有选择“使用图形 Alpha”,阴影并没有随着香蕉变暗,而是保持在指定的alpha 值价值。

All four bananas have the same alpha value set on their Shadow component’s Effect Color property. The Image component’s Color property of the first banana has the alpha set to full opacity. The second, third, and fourth bananas have the opacity of their Image component reduced. The second and third bananas have identical properties, except that the second banana uses the Use Graphic Alpha property and the third does not. So, you can see that the shadow of the third banana has not been dimmed by the dimming of the banana’s Image component. The fourth and final banana has its Image component’s opacity set to 0, but since Use Graphic Alpha is not selected on the Shadow component, the shadow did not dim with the banana and remains at its designated alpha value.

大纲

Outline

大纲组件模拟通过在指定距离处在图形周围创建四个阴影来在图形周围勾勒出轮廓

The Outline component simulates an outline around the graphic by creating four shadows around it at specified distances.

图 11.9:大纲组件

图 11.9:大纲组件

Figure 11.9: The Outline component

Outline组件会根据Effect Distance X在原图形的左右两侧创建两个阴影,并根据Effect Distance Y在原图形的上下两侧创建两个阴影。与Shadow组件不同,这两个距离的正负值没有区别,因为每个创建的两个阴影是镜像的。

The Outline component will create two shadows to the left and right of the original graphic based on Effect Distance X and two shadows to the top and bottom of the original graphic based on Effect Distance Y. Unlike the Shadow component, there is no difference in a negative or positive value for these two distances because the two shadows created for each axis are mirrored.

将效果距离 X值设置为-3本质上只是切换了两个水平阴影的位置,但效果看上去是一样的,如下图所示

Setting the Effect Distance X value to -3 essentially just switches the positions of the two horizontal shadows, but the effect looks the same, as shown in the following figure:

图 11.10:第 11 章场景中的大纲组件示例 1

图 11.10:第 11 章场景中的大纲组件示例 1

Figure 11.10: Outline Component Example 1 in the Chapter11 scene

此组件上的“使用图形 Alpha”属性与阴影组件上的“使用图形 Alpha”属性完全相同,如下所示:

The Use Graphic Alpha property works identically on this component as it does on the Shadow component, as shown:

图 11.11:第 11 章场景中的大纲组件示例 2

图 11.11:第 11 章场景中的大纲组件示例 2

Figure 11.11: Outline Component Example 2 in the Chapter11 scene

接下来我们看一下Position As UV1组件nent。

Next, let’s look at the Position As UV1 component.

位置为 UV1

Position As UV1

Position As UV1组件允许您更改 Canvas 渲染的 UV 通道。如果您想创建利用烘焙光照贴图的自定义着色器,可以使用此功能。

The Position As UV1 component allows you to change the UV channel that the Canvas renders on. This is used if you want to create custom shaders that utilize baked light maps.

图 11.12:Position As UV1 组件

图 11.12:Position As UV1 组件

Figure 11.12: The Position As UV1 component

遗憾的是,自定义着色器是一个非常沉重的话题,超出了本文的范围,因此我不会进一步讨论该组件的使用

Sadly, custom shaders are a pretty heavy topic and go past the scope of this text, so I won’t go any further into the usage of this component.

现在我们已经回顾了 UI 图像组件和一些 UI 效果组件,让我们看一些使用这些组件的方法的示例科目。

Now that we’ve reviewed the UI Image component and some UI effect components, let’s look at some examples of ways we can use these components.

示例

Examples

在本章中,我们将通过添加一些新的 UI 元素来进一步扩展我们一直在构建的场景。我们还将研究一些移动/触摸屏 UI和交互。

In this chapter, we’ll expand on the scene we’ve been building further by adding some new UI elements. We’ll also look at some mobile/touchscreen UI and interactions.

其中一些示例似乎更适合按钮章节,但由于它们包括对图像组件的属性的访问,因此我将它们放在这里。

Some of these examples may seem better suited for the chapter on Buttons, but since they include access to the Image component’s properties, I placed them here.

笔记

Note

我们创建了两个场景,它们将加载到我们一直在构建的场景中:一个开始屏幕和一个介绍场景。由于我一直在复制我们的主要场景,以便于跟踪每个章节的进度,因此我们的介绍场景将无法导航到我们在本章和未来章节中所做的更新,除非我们不断更新介绍场景中对话框组件上的下一个场景变量,并在我们的构建设置中包含新场景

We have created two scenes that load into the scene we’ve been building upon: a start screen and an intro scene. Since I’ve been duplicating our main scene to make progress from each chapter easy to track, our intro sScene will not navigate to the updates we make in this and future chapters unless we keep updating the Next Scene variable on our Dialogue Boxes component in the intro sScene and including the new scene in our Build Settings.

由于场景导航不再是这些示例的重点,因此我不会将此更新包括在步骤中。但是,它将包含在我在每个章节的完整 sc中包含的包中烯。

I will not be including this update in the steps since scene navigation is no longer a focus of these examples. However, it will be included in the packages I include in each chapter’s completed scenes.

水平和圆形健康/进度计

Horizontal and circular health/progress meters

让我们回到主场景。复制第 10 章示例场景以创建第 11 章示例场景。

Let’s get back to our main scene. Duplicate the Chapter10-Examples scene to create a Chapter11-Examples scene.

在本节中,我们将介绍如何创建两种类型的进度表,水平进度表和圆形进度表,如下面的屏幕截图所示:

In this section, we’ll cover how to create two types of progress meters, a horizontal one and a circular one, as shown in the following screenshot:

图 11.13:水平和垂直进度条示例

图 11.13:水平和垂直进度条示例

Figure 11.13: Example of horizontal and vertical progress bars

我们将连接圆形和水平的进度表,以便它们都显示同一变量的进度,并且我们可以同时看到它们的变化

We’ll hook up the circular and horizontal progress meters so that they both display the progress of the same variable, and we can watch them both change at the same time.

圆形进度条不太适合我们一直在构建的主场景,我们将在本章之后隐藏它,但圆形进度条是常见的游戏元素,所以我认为在本章中包含一个如何使用它们的示例很重要之三

The circular progress meter doesn’t really fit in the main scene that we’ve been building, and we’ll hide it after this chapter, but circular progress bars are common game elements, so I thought it was important to include an example of how to do them in this chapter.

水平健康条

Horizontal health bar

有几种不同的方法可以可以创建水平健康条,但最快捷、最简单的方法是根据百分比缩放单个轴。以这种方式设置水平健康条时,重要的是确保锚点设置在代表完全耗尽的健康条的位置。

There are a few different ways that a horizontal health bar can be created, but the quickest and easiest way is to scale a single axis based on percentage. When setting up a horizontal health bar in this way, it is important to ensure that the anchor is set at a position that represents a completely depleted bar.

记得在第 6 章中,我们将生命值条的锚点设置在左侧,因此我们已经正确设置了锚点。我们还在x方向上缩放了生命值条,以显示生命值条耗尽时的样子

Remember that back in Chapter 6, we set the anchor of the health bar to the left, so we have already set the anchor correctly. We also scaled the health bar in the x direction to show what the bar would look like as it depleted.

图 11.14:生命值条的矩形变换组件

Figure 11.14: The Health Bar’s Rect Transform component

现在,我们需要做的就是将百分比与健康条的X 比例值联系起来

Now, all we need to do is tie the percentage to the X Scale value of the health bar.

配合健康的填充bar 到实际值,请完成以下步骤:

To tie the fill of the health bar to an actual value, complete the following steps:

  1. 在Scripts文件夹中创建一个新的 C# 脚本并将其命名为ProgressMeters.cs
  2. Create a new C# script in your Scripts folder and name it ProgressMeters.cs.
  3. ProgressMeters脚本中,初始化以下四个变量:
    公共单位健康;
    [SerializeField] uint 总健康值;
    [SerializeField]浮动百分比健康;
    [SerializeField]RectTransform健康栏;

    health变量表示玩家当前的健康值,totalHealth变量表示玩家可以获得的总健康值。由于这些值为负数是没有意义的,因此它们被初始化为uint类型或正整数。我已将health变量设为公共变量,以便可以通过其他脚本访问并在 Inspector 中看到它。我将totalHealth设为私有SerializeField,以便它无法通过其他脚本访问,但仍可通过Inspector查看和分配。

    percentHealth变量将根据healthtotalHealth变量的商来计算。我将此值设为私有并序列化,这样做不是为了我们可以在 Inspector 中编辑它,而是为了我们可以在Inspector 中轻松看到其值的变化。

    healthBar变量存储Health Bar UI Image的RectTransform组件在我们的场景中

  4. In the ProgressMeters script, initialize the following four variables:
    public uint health;
    [SerializeField] uint totalHealth;
    [SerializeField] float percentHealth;
    [SerializeField] RectTransform healthBar;

    The health variable represents the current health of the player, and the totalHealth variable represents the total health the player can obtain. As it doesn’t make sense for these values to be negative, they have been initialized at the uint type or a positive integer. I have made the health variable public so that it can be accessed via other scripts and seen within the Inspector. I made totalHealth a private SerializeField so that it cannot be accessed via other scripts but still be seen and assigned via the Inspector.

    The percentHealth variable will be calculated based on the quotient of the health and totalHealth variables. I made this value private and serialized, not so that we can edit it in the Inspector but so that we can easily see its value change in the Inspector.

    The healthBar variable stores the RectTransform component of the Health Bar UI Image within our scene.

笔记

Note

由于RectTransform继承自Transform ,我们可以将healthBar声明为Transform,并且以下代码仍然有效。

Since RectTransform inherits from Transform, we could have declared healthBar as a Transform and the following code would still work.

  1. 返回 Unity 编辑器并将ProgessMeters脚本拖到HUD Canvas > Top Left Panel上。将值500分配给HealthTotal Health插槽。将Health Bar UI Image拖到Health Bar插槽中。您尝试在Percent Health插槽中输入的任何值都将被我们在下一步中编写的代码覆盖。您的组件应如下所示:
  2. Return to the Unity Editor and drag the ProgessMeters script onto HUD Canvas > Top Left Panel. Assign the value 500 to both the Health and Total Health slots. Drag the Health Bar UI Image into the Health Bar slot. Any value you try to type into the Percent Health slot will be overridden by the code we write in the next step. Your component should look as follows:
图 11.15:进度表组件

图 11.15:进度表组件

Figure 11.15: The Progress Meters component

  1. 我们希望对健康值所做的任何更改都能自动更新percentHealth值和healthBar的刻度。为此,我们可以将以下代码放入Update()函数中:
    无效更新()
    {
        // 上限健康
        如果 (健康 > 总健康)
        {
            健康=总健康;
        }
        // 计算健康百分比
        百分比健康 = (浮点数)健康 / 总健康;
        // 更新水平健康条
        healthBar.localScale = new Vector2(percentHealth, 1f);
    }

    使用uint类型声明healthtotalHealth变量可以防止它们变为负数,但我们仍需要为health变量设置上限。它超出totalHealth变量是没有意义的

    虽然percentHealth是一个浮点变量,但在两个uint变量之间执行除法将产生uint类型,因此在整数除法的开头添加(float)可提供除法浮点结果。

    代码的最后一部分设置healthBarlocalScale值。缩放 UI 对象时,必须使用localScale。这会在本地缩放对象,即相对于其父对象。

  2. We want any changes made to our health value to automatically update the percentHealth value and the scale of our healthBar. To do that, we can put the following code in the Update() function:
    void Update()
    {
        // Cap health
        if (health > totalHealth)
        {
            health = totalHealth;
        }
        // Calculate health percentage
        percentHealth = (float)health / totalHealth;
        // Update horizontal health bar
        healthBar.localScale = new Vector2(percentHealth, 1f);
    }

    Declaring our health and totalHealth variables with the uint type stopped them from becoming negative, but we still need to put an upper cap on our health variable. It doesn’t make sense for it to exceed the totalHealth variable.

    While percentHealth is a float variable, performing a division between two uint variables will result in a uint type, so adding (float) at the beginning of the integer division provides a float result from the division.

    The last part of the code sets the localScale value of the healthBar. When you scale a UI object, you have to use localScale. This scales the object locally, meaning relative to its parent object.

  3. 现在,我们可以在编辑器中轻松地测试代码了。运行游戏,将鼠标悬停在Progress Meters组件中的Health字样上,直到鼠标周围出现两个箭头,如下图所示:
  4. Now, we can test the code easily in the Editor. Play the game and hover your mouse over the word Health in the Progress Meters component until the mouse displays two arrows around it, as shown in the following figure:
图 11.16:进度表组件对仪表的影响

图 11.16:进度表组件对仪表的影响

Figure 11.16: The Progress Meters component’s effect on the meter

当这些箭头出现时,单击并拖动将根据鼠标位置操纵变量的值。执行此操作时,您会看到,随着健康值的减少,健康百分比值也会减少,并且场景中的健康栏大小会发生变化。您会注意到,您不能将健康值设置为低于0高于500

When these arrows appear, clicking and dragging will manipulate the values of the variable based on your mouse position. You’ll see, as you do this, that as the Health value decreases, the Percent Health value decreases, and the Health Bar in the scene changes size. You’ll note that you cannot set the value of Health below 0 or above 500.

如您所见,设置水平生命条并不困难。在生命值因事件而减少的游戏中复制此过程不需要很多步骤即可实现。只需确保正确设置生命条的锚点即可。此过程对于垂直生命条的工作方式类似 健康哈杆。

As you can see, setting up a horizontal health bar isn’t terribly difficult. Duplicating this process in a game where the health reduces by Events won’t require a lot of steps to achieve. Just ensure that you set the anchor of the health bar correctly. This process will work similarly for a vertical health bar.

圆形进度表

Circular progress meter

水平健康条的设置工作并不多。制作圆形进度条的工作同样简单,只需两行代码即可完成。由于我们的场景中还没有圆形进度条,因此我们必须先进行一些设置。

Horizontal health bars didn’t take a lot of work to set up. The work to make a circular progress meter is just about as easy and can be completed with only two more lines of code. Since we don’t already have a circular progress bar in our scene, we will have to start with a bit of setup first.

要创建圆形进度条,请完成以下步骤:

To create a circular progress bar, complete the following steps:

  1. 从代码包中,将circularMeter.png精灵拖到项目Sprites文件夹中。
  2. From the code bundle, drag the circularMeter.png sprite into the Sprites folder of your project.
  3. 将circularMeter.png精灵的Sprite Mode设置为Multiple ,并在Sprite Editor中自动对其进行切片
  4. Set the Sprite Mode of the circularMeter.png sprite to Multiple and automatically slice it in the Sprite Editor.
  5. 在层次结构中选择“左上面板”,并为其添加一个新的 UI Image 子项。将其命名为 Image Progress Holder
  6. Select the Top Left Panel within the Hierarchy and give it a new UI Image child. Name the Image Progress Holder.
  7. 与我们设置健康条的方式类似,会有一个容器和一个填充。将circularMeter_0子精灵拖到Progress HolderImage组件的Source Image槽中
  8. Similar to how we set up the health bar, there will be a holder and a fill. Drag the circularMeter_0 sub-sprite into the Source Image slot of the Image component of Progress Holder.
  9. 为我们的容器和填充图片获取正确的比例非常重要。因此,为了确保图片比例正确,请点击图片组件上的“设置原始尺寸”按钮
  10. It’s important that we get the right proportions for our holder and fill Images. So, to ensure that the Image is correctly proportioned, hit the Set Native Size button on the Image component.
  11. 现在,向进度条中添加一个名为“Progress Meter”的子 UI 图像
  12. Now, add a child UI Image to Progress Holder called Progress Meter.
  13. 将进度表的锚点预设设置为中间位置。不要拉伸它。
  14. Set the anchor preset of Progress Meter to middle center. Do not stretch it.
  15. 将circularMeter_1添加到进度表图像组件的源图像插槽中
  16. Add circularMeter_1 to the Source Image slot of the Image component on the Progress Meter.
  17. 同样点击进度表图像组件的“设置原始大小”按钮。完成此步骤后,您应该看到以下内容:
    图 11.17:进度表上的进度

    图 11.17:进度表上的进度

    如果粉色填充不完美位于蓝色支架内,您可能忘记点击其中一张图片上的“设置原始尺寸”按钮,或者进度表没有将其锚点预设设置为中间位置。

  18. Hit the Set Native Size button for the Progress Meter Image component as well. After completing this step, you should see the following:

    Figure 11.17: The progress on the Progress Meter

    If the pink fill is not perfectly nestled inside the blue holder, you may have forgotten to hit the Set Native Size button on one of the Images or the Progress Meter does not have its anchor preset set to middle center.

  1. 让我们移动这个仪表并稍微缩放一下。选择Progress Holder并将其移动到场景中Character Holder下方。将Progress Holder的Scale XScale Y值设置为0.8,使其稍微小一点。
  2. Let’s move this meter and scale it a bit. Select Progress Holder and move it so that it is positioned in the scene below Character Holder. Set the Scale X and Scale Y values of Progress Holder to 0.8 to make it a little smaller.
图 11.18:重新定位圆形进度表

图 11.18:重新定位圆形进度表

Figure 11.18: Repositioning the circular progress meter

  1. 对代码要做的最后一件事是更改进度表图像类型。更改将图像类型改为使用径向360 填充方法填充。将填充原点改为顶部。调整填充量上的滚动条以预览仪表填充:
  2. The last thing to do to the code is to change the Image Type of the Progress Meter. Change the Image Type to Filled with a Radial 360 Fill Method. Change the Fill Origin to Top. Adjust the scroll bar on the Fill Amount to preview the meter filling:
图 11.19:调整圆形进度表上的填充量

图 11.19:调整圆形进度表上的填充量

Figure 11.19: Adjusting the fill amount on the circular progress meter

  1. 现在,我们准备编写一些代码。正如您可能从调整Inspector 中的Fill Amount值中猜到的那样,我们希望将fillAmount值与代码中的percentHealth变量绑定在一起。首先,我们需要创建一个变量,通过它可以访问Progress Meter的图像组件。在代码顶部声明以下变量:
    [SerializeField] 图像进度条;
  2. Now, we’re ready to write some code. As you have probably guessed from adjusting the Fill Amount value in the Inspector, we’ll want to tie the fillAmount value to the percentHealth variable in our code. First, we need to create a variable with which we can access the Image component of our Progress Meter. Declare the following variable at the top of your code:
    [SerializeField] Image progressMeter;
  3. 现在,在Update()函数末尾添加以下内容
    // 圆形进度条
    progressMeter.fillAmount = percentHealth;
  4. Now, add the following at the end of the Update() function:
    // Circular progress meter
    progressMeter.fillAmount = percentHealth;
  5. 我们最不需要的要做的就是将进度表UI 图像挂接到进度表变量。将进度表拖入进度槽。
  6. The last thing we need to do is hook the Progress Meter UI Image to the progressMeter variable. Drag the Progress Meter into the Progress Meter slot.
图 11.20:进度表组件的更新

图 11.20:进度表组件的更新

Figure 11.20: The Progress Meter component’s updates

  1. 玩游戏并像之前一样调整检查器中的健康值,并观察两个仪表同步移动。
  2. Play the game and adjust the Health value in the Inspector as you did earlier, and watch the two meters move in unison.
图 11.21:进度表结果

图 11.21:进度表结果

Figure 11.21: Result of the Progress Meter

正如您所见,制作圆形进度条并不比制作水平进度条困难!

As you can see, making a circular progress meter is really not more difficult than making a horizontal one!

笔记

Note

就像我们使用圆形进度条的填充量一样,我们也可以使用水平健康条的填充量。将图像类型属性​​设置为填充水平,然后影响填充量值而不是比例,会产生类似的效果。

In the same way we used the fill amount for the circular progress bar, we could have used the fill amount for the horizontal health bar. Setting the Image Type property to Filled and Horizontal and then affecting the Fill Amount value rather than the scale would have had a similar effect.

因为这个圆形进度计不是原始 UI 计划的一部分,只是为了演示目的而放置在场景中,我将在未来的所有图形和屏幕中禁用它镜头。

Because this circular progress meter wasn’t part of the original UI plan and was only placed in the scene for demonstration purposes, I am going to disable it in all future figures and screenshots.

静音按钮与精灵交换

Mute Buttons with sprite swap

现在,让我们看一个例子,我们交换基于预定义状态的按钮精灵。这与我们在第 9 章中讨论的精灵交换过渡不同,因为它不会使用突出显示、按下、选中或禁用的状态。它包含在本章中,而不是按钮章中,因为它涉及影响图像组件,而不是按钮组件。

Now, let’s look at an example where we swap the sprite of a Button based on a pre-defined state. This is different than a sprite swap transition, which we discussed in Chapter 9, because it won’t use the states of highlighted, pressed, selected, or disabled. It’s included in this chapter rather than the Buttons chapter since it involves affecting the Image component, not the Button component.

在场景中,我们有一个暂停面板,当按下键盘上的P键时,它会弹出。在这个面板上,我们将放置两个静音按钮,一个用于音乐,一个用于声音,它们将在静音和非静音状态之间切换。面板将如以下屏幕截图所示:

In the scene, we have a Pause Panel that pops up when the P key is hit on the keyboard. On this Panel, we will place two mute Buttons, one for music and one for sound, which will toggle between muted and unmuted states. The Panel will appear as shown in the following screenshot:

图 11.22:带有新静音按钮的暂停面板

图 11.22:带有新静音按钮的暂停面板

Figure 11.22: The Pause Panel with new mute Buttons

添加音乐和声音按钮如上图所示,完成以下步骤:

To add the music and sound Buttons shown in the preceding screenshot, complete the following steps:

  1. 首先,我们需要引入一个新的艺术资产。我们之前导入的精灵表上的按钮精灵有点太小,并且不包含静音版本。因此,我对它们进行了一些编辑,并为您提供了一个新的精灵。在本书的源文件中,您应该找到一个名为muteUnmute.png的.png文件
    图 11.23:muteUnmute.png 精灵

    图 11.23:muteUnmute.png 精灵

    将此.png文件导入到项目的Assets/Sprites文件夹中。

  2. First, we need to bring in a new art asset. The Button sprites on the sprite sheet we imported previously are a bit too small and don’t contain a muted version. So, I edited them a bit and provided a new sprite for you. In the book’s source files, you should find an .png file named muteUnmute.png:

    Figure 11.23: The muteUnmute.png sprite

    Import this .png file into your project’s Assets/Sprites folder.

  1. 将精灵切成 mu通过将Sprite Mode更改为Multiple,打开Sprite Editor,然后应用自动切片类型,可以创建多个子 Sprite 。多个 Sprite 应该如下所示:
  2. Slice the sprite into multiple sub-sprites by changing its Sprite Mode to Multiple, opening its Sprite Editor, and applying the automatic slice type. Multiple sprites should appear as follows:
图 11.24:muteUnmute.png 精灵切片

图 11.24:muteUnmute.png 精灵切片

Figure 11.24: The muteUnmute.png sprite sliced

  1. 创建两个新按钮作为暂停面板的子项,并将它们命名为音乐按钮声音按钮。删除它们的文本子项,因为我们不需要它们。
  2. Create two new Buttons as children of the Pause Panel and name them Music Button and Sound Button. Delete their text children because we do not need them.
图 11.25:层次结构的当前视图

图 11.25:层次结构的当前视图

Figure 11.25: The current view of the Hierarchy

  1. 给两个新的按钮以下Rect TransformImage属性:
    图 11.26:两个按钮的 Rect Transform

    图 11.26:两个按钮的 Rect Transform

    您的面板现在应该看起来就像本示例开头的面板一样

    图 11.27:暂停面板

    图 11.27:暂停面板

  2. Give the two new Buttons the following Rect Transform and Image properties:

    Figure 11.26: The Rect Transform of the two Buttons

    Your Panel should now look just like the one at the beginning of this example:

    Figure 11.27: The Pause Panel

  1. 现在,让我们编写一些代码来让这些按钮交换代表声音的精灵,音乐打开和关闭。在Assets/Scripts文件夹中创建一个名为MuteUnmute.cs的新脚本

    将MuteUnmute的代码替换为以下内容:

    公共类 MuteUnmute : MonoBehaviour
    {
        [SerializeField] 按钮音乐按钮;
        私人图像音乐图像;
        [SerializeField] private Sprite[] musicSprites = new Sprite[2];
        私人 bool musicOn = true;
        [SerializeField] 按钮声音按钮;
        私人图像声音图像;
        [SerializeField] 私有 Sprite[] soundSprites = new Sprite[2];
        私人 bool soundOn = true;
        无效唤醒()
        {
            音乐图像 = 音乐按钮.GetComponent<图像>();
            声音图像 = 声音按钮.GetComponent<图像>();
        }
        公共无效切换音乐()
        {
            音乐开启 = !音乐开启;
            音乐图像.精灵 = 音乐精灵[Convert.ToInt32(musicOn)];
        }
        公共无效切换声音()
        {
            声音开启 = !声音开启;
            声音图像.精灵 = 声音精灵[Convert.ToInt32(soundOn)];
        }
    }
  2. Now, let’s write some code to make these Buttons swap sprites that will represent the sound and music toggling on and off. Create a new script called MuteUnmute.cs in your Assets/Scripts folder.

    Replace the code of MuteUnmute with the following:

    public class MuteUnmute : MonoBehaviour
    {
        [SerializeField] Button musicButton;
        private Image musicImage;
        [SerializeField] private Sprite[] musicSprites = new Sprite[2];
        private bool musicOn = true;
        [SerializeField] Button soundButton;
        private Image soundImage;
        [SerializeField] private Sprite[] soundSprites = new Sprite[2];
        private bool soundOn = true;
        void Awake()
        {
            musicImage = musicButton.GetComponent<Image>();
            soundImage = soundButton.GetComponent<Image>();
        }
        public void ToggleMusic()
        {
            musicOn = !musicOn;
            musicImage.sprite = musicSprites[Convert.ToInt32(musicOn)];
        }
        public void ToggleSound()
        {
            soundOn = !soundOn;
            soundImage.sprite = soundSprites[Convert.ToInt32(soundOn)];
        }
    }

如你所见,此代码包含两个主要函数:ToggleMusic()ToggleSound()。这两个函数完全相同,只是根据musicOnsoundOn 布尔值交换指定按钮上的精灵。

As you can see, this code contains two main functions: ToggleMusic() and ToggleSound(). These two function identically by simply swapping the sprite on the specified Button based on the musicOn and soundOn Boolean values.

要交换精灵,脚本首先在Awake()函数中查找指定为musicButtonsoundButton的两个按钮上的 Image 组件。这些按钮将在 Inspector 中分​​配。然后它将 Image 组件的精灵交换为正确的精灵来自精灵数组。静音和取消静音状态的精灵将在后续步骤中在 Inspector 中分​​配。

To swap the sprite, the script first finds the Image component on the two Buttons specified as musicButton and soundButton, within the Awake() function. These Buttons will be assigned in the Inspector. It then swaps the sprite of the Image component to the correct sprite from an array of sprites. The sprites for the mute and unmute states will be assigned in the Inspector in a future step.

笔记

Note

遗憾的是,本书没有介绍如何向 Unity 项目添加声音和音乐。此处提供的代码实际上并没有静音和取消静音;它只是交换精灵。您只需包含两个音频源:一个用于播放音乐,一个用于播放分别带有MusicSound 标签的声音。

Sadly, this book does not cover adding sound and music to a Unity project. The code provided here doesn’t actually mute and unmute audio; it simply swaps sprites. You will simply need to include two audio sources: one for playing music and one for playing sounds that have the Music and Sound tags, respectively.

  1. 返回 Unity 编辑器并将MuteUnmute.cs脚本拖入暂停面板的检查器 (Inspector)
  2. Go back to the Unity Editor and attach the MuteUnmute.cs script to the Pause Panel by dragging it into its Inspector:
图 11.28:静音取消静音组件

图 11.28:静音取消静音组件

Figure 11.28: The Mute Unmute component

  1. 现在,我们要将适当的按钮和精灵分配到插槽中。将音乐按钮声音按钮从层次结构拖到其指定的插槽中。拖放将音频按钮精灵从项目视图移动到其适当的插槽,确保将静音精灵放在数组的 0 元素中。
  2. Now, we want to assign the appropriate Buttons and sprites to the slots. Drag the Music Button and Sound Button into their designated slots from the Hierarchy. Drag and drop the audio Button sprites from the project view to their appropriate slots, making sure to put the muted sprite in the 0 element of the array.
图 11.29:更新后的静音取消静音组件

图 11.29:更新后的静音取消静音组件

Figure 11.29: The updated Mute Unmute component

  1. 现在,我们只需连接按钮即可调用相应的功能。选择音乐按钮。选择按钮组件的OnClick()事件列表底部的+号以添加新事件。我们要访问的脚本MuteUnmute.cs位于暂停面板上,因此将暂停面板拖到对象槽中。现在,从功能下拉菜单中选择MuteUnmute | ToggleMusic
  2. Now, we just need to hook up the Buttons to call the appropriate functions. Select the Music Button. Select the + sign at the bottom of the OnClick() Event list of the Button component to add a new Event. The script we want to access, MuteUnmute.cs, is on the Pause Panel, so drag the Pause Panel into the object slot. Now, from the function dropdown menu, select MuteUnmute | ToggleMusic.
  3. 执行相同操作就像您在上一步中对声音按钮所做的那样,但这次从功能下拉列表中选择MuteUnmute | ToggleSound
  4. Perform the same actions as you did in the previous step for the Sound Button, but this time select MuteUnmute | ToggleSound from the function dropdown list.

现在,玩游戏,按P调出暂停面板,你会看到按钮在两个不同的状态之间来回切换。公主。

Now, play the game, press P to bring up the Pause Panel, and you will see the Buttons toggle back and forth between their two different sprites.

现在我们已经了解了如何实现进度条和精灵交换按钮,接下来让我们看看如何实现一些不同的移动设备特定交互。

Now that we’ve looked at how to implement progress meters and sprite swap Buttons, let’s look at how to implement a few different mobile-specific interactions.

添加按住/长按功能

Adding press-and-hold/long-press functionality

按住在手机游戏中使用频率很高。许多在 PC 或 Web 上使用右键的游戏在转换到手机平台时也使用按住。

Press-and-hold is utilized frequently in mobile games. Many games that use right-click on a PC or the web use press-and-hold when they are converted to the mobile platform.

演示如何实现按住功能,我们将创建一个按钮,该按钮有一个表示按住时间的不断增大的圆环。一旦经过指定的时间量,就会触发一个函数:

To demonstrate how to implement press-and-hold functionality, we will create a Button that has a growing ring that represents hold time. Once a specified amount of time has passed, a function will fire:

图 11.30:按住按钮示例

图 11.30:按住按钮示例

Figure 11.30: Press-and-hold Button example

在处理此示例时,请务必记住,即使代码引用了指针,此功能也并非只适用于鼠标。将手指放在触摸屏上的作用与指针向下的作用相同,而抬起手指的作用与指针向上的作用相同。

When working on this example, it is important to remember that even though the code is referencing a pointer, this functionality does not work exclusively with a mouse. Placing a finger on a touchscreen functions in the same way as a pointer down, and picking up the finger works the same as a pointer up.

要创建一个带有表示按住时间的不断增大的环的按钮,请完成以下步骤:

To create a Button with a growing ring that represents hold time, complete the following steps:

  1. Assets/Scenes文件夹中创建一个名为Chapter11-Examples-Buttons1的新场景并打开该新场景。
  2. Create a new scene named Chapter11-Examples-Buttons1 in the Assets/Scenes folder and open the new scene.
  3. 选择+ | UI | Button在场景中创建一个新的按钮。
  4. Select + | UI | Button to create a new Button in the scene.
  5. 将按钮组件上的按钮转换类型设置None
  6. Set the Button’s Transition type on the Button component to None.
  7. 将按钮上的文字更改为按下并按住
  8. Change the text on the Button to say Press and Hold.
  9. 右键单击层次结构中的按钮并选择UI |图像按钮添加一个图像子项
  10. Right-click the Button in the Hierarchy and select UI | Image to add an Image child to the Button.
  11. 将图像矩形变换组件上的宽度高度更改50
  12. Change the Width and Height on the Image’s Rect Transform component to 50.
  13. 将circularMeter_1精灵分配给Image组件的Source Image属性
  14. Assign the circularMeter_1 sprite to the Source Image property of the Image component.
  15. 将图像类型更改为填充,并将填充量 更改0
  16. Change the Image Type to Filled and change the Fill Amount to 0.
  17. 为了在按钮上创建按住功能,我们将利用指针向下指针向上事件。将事件触发器组件添加到按钮对象。
  18. To create press-and-hold functionality on the Button, we will utilize the Pointer Down and Pointer Up Events. Add the Event Trigger component to the Button object.
  19. 选择添加新事件类型选择PointerDown
  20. Select Add New Event Type and select PointerDown.
  21. 选择添加新事件类型选择PointerUp
  22. Select Add New Event Type and select PointerUp.
  23. 现在,我们需要实际编写之前设置的事件触发器将调用的函数步骤。在Assets/Scripts文件夹中创建一个名为LongPressButton的新脚本。
  24. Now, we need to actually write the functions that will be called by the Event Triggers we set up in the previous steps. Create a new script in the Assets/Scripts folder called LongPressButton.
  25. 在打开脚本之前,请先将其作为组件附加Button
  26. Before opening the script, go ahead and attach it as a component to Button.
  27. 使用以下命令将UnityEngine.UI命名空间添加到脚本顶部:
    使用 UnityEngine.UI;
  28. Add the UnityEngine.UI namespace to the top of the script with the following:
    using UnityEngine.UI;
  29. 要检查按钮被按下的时间,我们将使用布尔变量来检查按钮是否被按住,以及一些与时间相关的不同变量。将以下变量声明添加到脚本中:
    私有 bool buttonPressed = false;
    私有浮点开始时间 = 0f;
    私有浮点保持时间 = 0f;
    [SerializeField] 私有 float longHoldTime = 1f;

    buttonPressed变量将在Pointer Down事件中设置为true,在Pointer Up事件中设置为 false。startTime 变量将在触发 Pointer Down 事件时设置为当前时间。holdTime变量确定自startTime以来已经过了多少时间。longHoldTime变量是按完成之前必须按住按钮的时间。它是序列化的,因此可以轻松自定义。

  30. To check how long the Button is being pressed, we will use a Boolean variable that checks to see if the Button is being held and a few different variables related to time. Add the following variable declaration to your script:
    private bool buttonPressed = false;
    private float startTime = 0f;
    private float holdTime = 0f;
    [SerializeField] private float longHoldTime = 1f;

    The buttonPressed variable will be set to true with the Pointer Down Event and false with the Pointer Up Event. The startTime variable will be set to the current time when the Pointer Down Event is triggered. The holdTime variable will determine how much time has passed since startTime. The longHoldTime variable is the amount of time the Button must be held down before the long press is complete. It is serialized so that it can be easily customized.

  31. 我们需要的最后一个变量将代表径向填充图像。添加以下变量代码声明:
    [SerializeField] 私有图像径向填充图像;
  32. The last variable we need will represent the radial filling Image. Add the following variable declaration to your code:
    [SerializeField] private Image radialFillImage;
  33. 现在,我们需要编写一个函数,该函数将由指针向下指针向上事件调用
    公共无效PressAndRelease(bool pressStatus)
    {
        按钮按下=按下状态;
        如果(!按钮按下)
        {
            保持时间=0;
            径向填充图像.填充量 = 0;
        }
        别的
        {
            开始时间 = 时间.时间;
        }
    }

    此函数从事件触发器接受一个布尔变量。然后它将buttonPressed的值设置为传递的值。

    释放按钮时,将向函数传递false值。如果传递的值为false,则已过去的时间量holdTime将重置为0,并且将radiusFillImage图像重置fillAmount0

    当按下按钮时,startTime值将设置为当前时间。

  34. Now, we need to write a function that will be called by both the Pointer Down and Pointer Up Events:
    public void PressAndRelease(bool pressStatus)
    {
        buttonPressed = pressStatus;
        if (!buttonPressed)
        {
            holdTime = 0;
            radialFillImage.fillAmount = 0;
        }
        else
        {
            startTime = Time.time;
        }
    }

    This function accepts a Boolean variable from the Event Trigger. It then sets the value of buttonPressed to the passed value.

    When the Button is released, a value of false will be passed to the function. If the value passed is false, the amount of time that has passed, holdTime, is reset to 0, and the radialFillImage Image is reset to have a fillAmount value of 0.

    When the Button is pressed, the startTime value will be set to the current time.

  35. 创建一个函数,该函数将在长按所需的全部时间(由longHoldTime指定)完成后调用:
    公共无效LongPressCompleted()
    {
        径向填充图像.填充量 = 0;
        Debug.Log("长按后执行某些操作");
    }

    此函数实际上不执行任何操作,只是重置填充图像并打印出Debug.Log。但是,您以后可以重复使用此代码,并用更有趣和更有意义的操作替换Debug.Log行

  36. Create a function that will be called once the full amount of time needed for the long press, specified by longHoldTime, has completed:
    public void LongPressCompleted()
    {
        radialFillImage.fillAmount = 0;
        Debug.Log("Do something after long press");
    }

    This function doesn’t really do anything but reset the filling Image and print out a Debug.Log. However, you can later reuse this code and replace the Debug.Log line with more interesting and meaningful actions.

  37. Update()函数可用于使计时器向上计数。调整Update()函数如下:
    无效更新()
    {
        如果(按钮按下)
        {
            保持时间 = 时间.时间 - 开始时间;
            如果 (保持时间 >= 长保持时间)
            {
                按钮按下=false;
                长按完成();
            }
            别的
            {
                径向填充图像.填充量 = 保持时间 / 长保持时间;
            }
        }
    }

    如果buttonPressed值设置为true ,此代码会使holdTime的值向上跳动。请记住 - buttonPressed将通过指针向下事件设置为true ,通过指针向上事件设置为false。因此,只有当玩家按下按钮但尚未释放时,它才会为 true

    一旦holdTime值达到longHoldTime指定的值,计时器将停止计时,因为buttonPressed将被重置为false。此外,还会调用LongPressCompleted()函数。如果尚未达到longHoldTime ,则图像的径向填充将更新以表示已过的总所需时间百分比。

  38. The Update() function can be used to make the timer count upward. Adjust the Update() function as follows:
    void Update()
    {
        if (buttonPressed)
        {
            holdTime = Time.time - startTime;
            if (holdTime >= longHoldTime)
            {
                buttonPressed = false;
                LongPressCompleted();
            }
            else
            {
                radialFillImage.fillAmount = holdTime / longHoldTime;
            }
        }
    }

    This code makes the value of holdTime tick upward if the buttonPressed value is set to true. Remember—buttonPressed will be set to true with a Pointer Down Event and false with a Pointer Up Event. So, it will only be true if the player has pressed the Button and not yet released it.

    Once the holdTime value reaches the value specified by longHoldTime, the timer will stop ticking up, because buttonPressed will be reset to false. Additionally, the LongPressCompleted() function is called. If longHoldTime has not yet been reached, the Image’s radial fill will update to represent the percentage of total required time that has transpired.

  39. 现在脚本已完成,我们可以将PressAndRelease()函数与按钮上的事件触发器连接起来。将静态列表中的PressAndRelease函数添加到Pointer DownPointer Up事件触发器。由于PressAndRelease()函数接受布尔变量,因此有一个复选框表示应传递的布尔值。选择Pointer Down事件的复选框(发送true),但不要选择Pointer Up事件的复选框(发送false)。
  40. Now that the script is completed, we can hook up the PressAndRelease() function with the Event Triggers on the Button. Add the PressAndRelease function from the static list to both the Pointer Down and Pointer Up Event Triggers. Since the PressAndRelease() function accepts a Boolean variable, there is a checkbox representing the Boolean value that should be passed. Select the checkbox for the Pointer Down Event (sending true) but not for the Pointer Up Event (sending false).
图 11.31:事件触发器组件

图 11.31:事件触发器组件

Figure 11.31: The Event Trigger component

  1. 现在,我们需要将图像分配给组件上的径向填充图像插槽
  2. Now, we need to assign the Image to the Radial Fill Image slot on the Long Press component.
图 11.32:长按组件

图 11.32:长按组件

Figure 11.32: The Long Press component

现在玩游戏将演示按住按钮时图像径向填充,并在控制台中长按后打印“执行某些操作”。如果在填充完成之前释放按钮,它将消失并在您再次开始单击时重置。

Playing the game now will demonstrate the Image radially filling when you hold the Button and printing Do something after long press in the console. If you release the Button before the fill has completed, it will go away and reset for when you start clicking again.

按住是一项非常常见的功能,虽然它不是 Unity 事件库中预安装的事件,但幸运的是,连接起来并不太难。我建议保留该脚本,以便您可以未来。

Press-and-hold is a pretty common functionality, and while it isn’t a pre-installed Event in the Unity Event library, luckily it isn’t too difficult to hook up. I recommend holding on to that script so that you can reuse it in the future.

创建静态四向虚拟方向键

Creating a static four-directional virtual D-Pad

D-Pad 就是方向键上的四个按钮。要为移动游戏创建 D-Pad,您只需创建一个包含四个方向按钮的图形。

A D-Pad is simply four Buttons on a directional pad. To create a D-Pad for a mobile game, you just need to create a graphic that contains four Buttons on the directions.

本例中使用的艺术作品来自https://opengameart.org/content/onscreen-controls-8-styles

The art used in this example was obtained from https://opengameart.org/content/onscreen-controls-8-styles.

要创建虚拟 D-Pad,请完成以下步骤:

To create a virtual D-Pad, complete the following steps:

  1. Assets/Scenes文件夹中创建一个名为Chapter11-Examples-Buttons2的新场景并打开该新场景。
  2. Create a new scene named Chapter11-Examples-Buttons2 in the Assets/Scenes folder and open the new scene.
  3. dPadButtons.png精灵表导入Assets/Sprites文件夹。
  4. Import the dPadButtons.png sprite sheet into the Assets/Sprites folder.
  5. 将新导入的精灵的Sprite Mode改为Multiple,并自动切片。
  6. Change the newly imported sprite’s Sprite Mode to Multiple and automatically slice it.
  7. 使用+ | UI | Canvas创建一个新的 Canvas 。将新 Canvas 命名为D-Pad Canvas
  8. Create a new Canvas with + | UI | Canvas. Name the new Canvas D-Pad Canvas.
  9. 在移动设备上,方向键的尺寸非常重要。即使屏幕变小,您可能也希望方向键的尺寸大致相同。如果方向键太小,游戏可能无法玩或不舒服。因此,请将Canvas Scaler组件的UI Scale Mode值设置为Constant Physical Size
  10. The size of a D-Pad is incredibly important on mobile devices. Even if the screen gets smaller, you’ll probably want the D-Pad to be about the same size. If it gets too small, the game can be unplayable or uncomfortable. Therefore, set the Canvas Scaler component’s UI Scale Mode value to Constant Physical Size.
  11. 使用+ | UI | Image添加一个新的 Image 作为D-Pad Canvas的子项,并将其重命名为D-Pad Background
  12. Add a new Image as a child of D-Pad Canvas with + | UI | Image and rename it D-Pad Background.
  13. 将源图像 设置dPadButtons_4
  14. Set the Source Image to dPadButtons_4.
  15. 将其锚点和枢轴设置为屏幕的左下角,并将其Pos XPos Y值设置30
  16. Set its anchor and pivot to the lower-left corner of the screen, and set its Pos X and Pos Y values to 30.
  17. 将其宽度高度设置 200
  18. Set its Width and Height to 200:
图 11.33:方向键正确定位

图 11.33:方向键正确定位

Figure 11.33: The D-Pad positioned correctly

  1. 右键单击D-Pad Background并使用UI | Button添加一个新按钮作为子按钮。将新按钮重命名为Up
  2. Right-click on D-Pad Background and add a new Button as a child with UI | Button. Rename the new Button Up.
  3. 删除其子文本对象。
  4. Remove its child Text object.
  5. 将“向上”按钮的Pos XPos YWidthHeight值分别设置为0656060。这将在 D-Pad 图像的上方位置创建一个正方形
  6. Set the Pos X, Pos Y, Width, and Height values of the Up Button to 0, 65, 60, and 60, respectively. This will create a square over just the up position of the D-Pad Image.
图 11.34:带有向上按钮的方向键

图 11.34:带有向上按钮的方向键

Figure 11.34: The D-Pad with an Up Button

  1. 复制“向上”按钮三次,并将重复项重命名为“右”“左”下”
  2. Duplicate the Up Button three times and rename the duplicates Right, Left, and Down.
  3. 将右按钮的Pos XPos Y值分别设置为650
  4. Set the Right Button’s Pos X and Pos Y values to 65 and 0, respectively.
  5. 将左按钮的Pos XPos Y值分别设置为-650
  6. Set the Left Button’s Pos X and Pos Y values to -65 and 0, respectively.
  7. 将Down按钮的Pos XPos Y值分别设置为0-65。现在你应该有四个按钮位置如下:
    图 11.35:方向键及其所有按钮

    图 11.35:方向键及其所有按钮

    这四个按钮覆盖了方向键臂的整个区域。它们将充当方向的点击区域。

  8. Set the Down Button’s Pos X and Pos Y values to 0 and -65, respectively. You should now have four Buttons positioned as follows:

    Figure 11.35: The D-Pad with all of its Buttons

    These four Buttons cover the entire area of the arms of the directional pad. They will act as the hit area for the directions.

  1. 我们实际上只希望这四个按钮位于它们的点击区域,而不希望它们在 UI 中实际可见。选择层次结构中的所有四个按钮,并将其图像组件的Color属性上的 alpha 值设置0
  2. We only really want these four Buttons for their hit area and don’t want to actually have them visible in the UI. Select all four of the Buttons in the Hierarchy and set the alpha value on the Color property of their Image component to 0.
  3. 由于方向键图像是静态的,没有分成四个单独的按钮,因此对其应用的任何过渡都会覆盖整个图像。但是,我们可以为方向键上的箭头添加子图像,使各个方向看起来好像被按下了一样,并具有某种颜色过渡。右键单击“向上”按钮,然后使用UI | Image添加一个图像作为子图像。将新的图像重命名为Arrow
  4. Since the directional pad Image is static and not split into four separate Buttons, any transitions applied to it would cover the whole Image. However, we can make the individual directions look as if they are being pressed and have some sort of color transition by adding sub-Images for the arrows on the directions. Right-click the Up Button and add an Image as a child with UI | Image. Rename the new Image Arrow.
  5. dPadButtons_5精灵分配给其图像组件上的源图像,然后选择保留纵横比
  6. Assign the dPadButtons_5 sprite to the Source Image on its Image component and select Preserve Aspect.
  7. 缩放并移动图像,使其与 D-Pad 背景图像上显示的箭头适当对齐
  8. Scale and move the Image so that it is appropriately lined up with the arrow displayed on the D-Pad background Image:
图 11.36:方向键的向上箭头

图 11.36:方向键的向上箭头

Figure 11.36: The Up Arrow of the D-Pad

  1. 选择箭头图像组件上的颜色插槽,然后使用滴管工具从D-Pad 背景图像中获取箭头的颜色。这会使其变为浅灰色而不是白色。
  2. Select the Color slot on the Arrow Image component and use the eye dropper tool to grab the color of the arrows from the D-Pad Background Image. This will make it a light gray instead of white.
  3. 创建Arrow子项为其他三个按钮设置适当的大小、位置和颜色。确保还使用了dPadButtons精灵表的正确子图像。完成后,您的 D-Pad 和层次结构应如下所示:
  4. Create Arrow children for each of the other three Buttons, and size, position, and color them appropriately. Make sure to also use the correct sub-Image of the dPadButtons sprite sheet. Once completed, your D-Pad and Hierarchy should appear as follows:
图 11.37:方向键上的所有箭头

图 11.37:方向键上的所有箭头

Figure 11.37: All Arrows on the D-Pad

  1. 现在,为了让方向键在按下四个方向时做出视觉反应,我们将设置四个箭头子项,使其在按下四个按钮时具有颜色按钮过渡。对于每个按钮,将其箭头子项拖到其按钮组件上的“目标图形”插槽中。现在,当您按下单个按钮时,您将看到箭头颜色略有变化,指示哪个方向按下。如果您难以判断是否发生了变化,您可能希望将按下的颜色更改为比默认灰色更鲜明的颜色。
  2. Now, so that the D-Pad will react visually when the four directions are pressed, we will set the four Arrow children to have color tint Button Transitions when the four Buttons are pressed. For each Button, drag its Arrow child into the Target Graphic slot on its Button component. Now, when you press the individual Buttons, you will see a slight change in the color of the arrows, indicating which direction is pressed. You may wish to change the pressed color to something a bit more drastic than the default gray if you are having difficulty telling that a change is occurring.
  3. 将本书代码包中的脚本DPad.cs添加到Assets/Scripts文件夹中。这是一个非常简单的脚本,包含四个仅写入控制台的函数。将这四个函数连接到各个方向按钮不​​会产生任何有趣的效果,但它可以让我们在控制台中看到日志,让我们知道按钮的运行情况正常
  4. Add the script named DPad.cs from the book’s code bundle to the Assets/Scripts folder. This is an incredibly simple script that contains four functions that only write to the console. Hooking up these four functions to the individual directional Buttons won’t do anything fun, but it will allow us to see logs in the console that let us know the Buttons are performing as they should.
  5. 将脚本附加到D-Pad Background对象。
  6. Attach the script to the D-Pad Background object.
  7. 选择四个方向按钮中的每一个,并在选择所有按钮的情况下,为按钮组件添加On Click ()事件
  8. Select each of the four directional Buttons and, with all of them selected, add an On Click () Event to the Button component.
  9. 现在,将D-Pad Background对象拖入On Click () 事件的对象槽中
  10. Now, drag the D-Pad Background object into the object slot of the On Click () Event.
  11. 单独选择每个按钮并将适当的功能PressUpPressDownPressLeftPressRight分配给它们的On Click () 事件。
  12. Select each Button individually and assign the appropriate functions, PressUp, PressDown, PressLeft, and PressRight, to their On Click () Events.

玩游戏并选择四个方向按钮应该会显示相应的消息控制台上。

Playing the game and selecting the four directional Buttons should result in the appropriate message being displayed on the console.

许多方向键实际上接受九个输入:四个方向键、四个对角线(角)和中心。如果您希望方向键既接受对角线输入,又接受中心单击,我建议使用网格布局组来均匀分布九个按钮。

Many D-Pads actually accept nine inputs: the four directs, the four diagonals (corners), and the center. If you want to accept diagonal inputs as well as a center-click for your D-Pad, I’d suggest using a grid layout group to evenly space your nine Buttons.

由于方向键通常允许按住,因此您可能希望将此示例中使用的过程与上一个示例中描述的类似操作相结合。您可以设置事件触发器来使用OnPointerDownOnPointerUp事件,而不是使用On Click ()事件。然后,这些事件可以将布尔变量设置为truefalse。例如,在按钮上,您可以让OnPointerDown事件将名为moveRight 的变量设置为true,让OnPointerUp事件将moveRight设置false

Since D-Pads tend to allow press-and-hold, you may want to combine the process used in this example with actions similar to those described in the previous example. Instead of using the On Click () Event, you could set up an Event Trigger for using the OnPointerDown and OnPointerUp Events. These Events could then set a Boolean variable to true and false. For example, on the Right Button, you could have the OnPointerDown Event set a variable called moveRight to true and the OnPointerUp Event set moveRight to false.

创建浮动的八向虚拟模拟摇杆

Creating a floating eight-directional virtual analog stick

在此示例中,我们将创建一个浮动的八向虚拟模拟摇杆。首先,我们将创建一个八向 D-Pad,模拟沿玩家拖动方向移动的控制杆

In this example, we will create a floating eight-directional virtual analog stick. First, we will create an eight-directional D-Pad that simulates a control stick that moves in the direction the player drags:

图 11.38:浮动摇杆的位置

图 11.38:浮动摇杆的位置

Figure 11.38: The positions of the floating analog stick

然后,我们将扩展八向 D-Pad,使其浮动,这意味着在玩家按下屏幕中的某个位置之前,它不会在场景中可见。然后,它将出现在玩家的拇指的位置,并根据玩家的拇指进行八个方向的移动拖拽。

Then, we will expand the eight-directional D-Pad so that it is floating, which means it will not be visible in the scene until the player presses somewhere in the screen. Then, it will appear where the player’s thumb is located and perform the eight-direction movement based on the player’s thumb dragging.

设置八向虚拟模拟摇杆

Setting up the eight-directional virtual analog stick

创建一个以八次为单位移动的模拟摇杆方向,如上图所示,完成以下步骤:

To create an analog stick that moves in eight directions, as shown in the previous figure, complete the following steps:

  1. Assets/Scenes文件夹中创建一个名为Chapter11-Examples-Buttons3的新场景并打开该新场景。
  2. Create a new scene named Chapter11-Examples-Buttons3 in the Assets/Scenes folder and open the new scene.
  3. 使用+ | UI | Image创建新图像。将新图像命名为 Image Stick Base
  4. Create a new Image with + | UI | Image. Name the new Image Stick Base.
  5. 将dPadButtons_15精灵添加到其图像组件的源图像槽中
  6. Add the dPadButtons_15 sprite to the Source Image slot of its Image component.
  7. 调整其大小,使其宽度高度200,并使其Pos XPos Y 0
  8. Resize it so that it has a Width and Height of 200 and give it a Pos X and Pos Y of 0.
  9. 在 Hierarchy 中右键单击Stick Base并选择UI | Image为Stick Base添加一个Image子项。将子项命名为Stick
  10. Right-click Stick Base in the Hierarchy and select UI | Image to add an Image child to the Stick Base. Name the child Stick.
  11. 通过设置 Stick Image 的Rect Transform拉伸和锚点以在两个方向上完全拉伸,调整Stick Image 的大小以匹配Stick Base
  12. Resize the Stick Image to match the Stick Base by setting its Rect Transform stretch and anchor to stretch fully across both directions.
  13. 将dPadButtons_0精灵添加到Stick Image 组件的Source Image插槽
  14. Add the dPadButtons_0 sprite to the Source Image slot of the Stick Image component.
  15. Rect Transform组件的LeftTopRightBottom属性全部设置为10,以便在 Stick边缘周围留出一些填充
  16. Set the Left, Top, Right, and Bottom properties of the Rect Transform component all to 10 to give some padding around the edges of the Stick.
  17. 现在,设置枢轴和位置到中间。这是重要的一步!如果不这样做,摇杆将无法在摇杆底座上适当移动
  18. Now, set the pivot and position to middle center. This is an important step! Without doing this, the Stick will not move around appropriately on the Stick Base.
图 11.39:Stick 的 Rect Transform 组件

图 11.39:Stick 的 Rect Transform 组件

Figure 11.39: The Rect Transform component of the Stick

  1. 这就是让我们的虚拟摇杆工作所需的全部设置。我们暂时将它放在屏幕中央。现在,我们需要编写一些代码。在Assets/Script文件夹中创建一个新脚本并将其命名为FloatingAnalogStick
  2. That’s all there is for the setup to get our virtual analog stick working. We’ll just leave it in the center of the screen for now. Now, we need to write some code. Create a new script in the Assets/Script folder and name it FloatingAnalogStick.
  3. 使用以下命令将UnityEngine.UI命名空间添加到脚本顶部:
    使用 UnityEngine.UI;
  4. Add the UnityEngine.UI namespace to the top of the script with the following:
    using UnityEngine.UI;
  5. 让棍子摆动在基座顶部,我们需要以下变量:
    [SerializeField] 私有 RectTransform theStick;
    私人向量2鼠标起始位置;
    私人Vector2鼠标当前位置;
    [SerializeField] 私有 int dragPadding = 30;

    前三个变量的含义非常明确。dragPadding变量用于确定玩家必须拖动操纵杆多远才能真正将其移动

  6. To make the stick wiggle around on top of the base, we need the following variables:
    [SerializeField] private RectTransform theStick;
    private Vector2 mouseStartPosition;
    private Vector2 mouseCurrentPosition;
    [SerializeField] private int dragPadding = 30;

    The first three variables should be pretty self-explanatory. The dragPadding variable will be used to determine how far the player has to drag the stick before it actually registers as being moved.

  7. 在我们编写检查玩家手指拖动距离的代码之前,让我们添加一些虚拟函数,以便让这个模拟摇杆将来能够实际控制某些东西。将以下函数添加到您的脚本中:
    公共无效移动左()
    {
        Debug.Log("向左移动");
    }
    公共无效移动右()
    {
        Debug.Log("向右移动");
    }
    公共无效移动()
    {
        Debug.Log("向上移动");
    }
    公共无效移动向下()
    {
        Debug.Log("向下移动");
    }
  8. Before we write the code that checks how far the player has dragged their finger, let’s add a few dummy functions that would allow this analog stick to actually control something in the future. Add the following functions to your script:
    public void MovingLeft()
    {
        Debug.Log("move left");
    }
    public void MovingRight()
    {
        Debug.Log("move right");
    }
    public void MovingUp()
    {
        Debug.Log("move up");
    }
    public void MovingDown()
    {
        Debug.Log("move down");
    }
  9. 当玩家将手指从起始手指向下位置移开时,摇杆向外移动。因此,让我们创建一个函数,当玩家开始拖动手指时,该函数将找到起始位置。将以下函数添加到您的脚本中:
    公共无效StartDrag()
    {
        鼠标起始位置 = 输入.鼠标位置;
    }

    请记住 - 使用触摸屏时,Input.mousePosition将提供触摸位置的值

  10. The Stick will move outward when the player moves their finger from their starting finger-down position. So, let’s create a function that will find the starting position when the player begins dragging their finger. Add the following function to your script:
    public void StartDrag()
    {
        mouseStartPosition = Input.mousePosition;
    }

    Remember—when working with a touchscreen, Input.mousePosition will give the value of the touch position.

  11. 现在,让我们创建一个函数来检查玩家拖动手指的距离,并根据该信息移动Stick 。将以下函数添加到脚本中:
    公共无效拖动()
    {
        浮动xPos;
        浮动yPos;
        鼠标当前位置 = 输入.鼠标位置;
        如果 (mouseCurrentPosition.x < mouseStartPosition.x - dragPadding)
        {
            向左移动();
            x位置=-10;
        }
        否则,如果(mouseCurrentPosition.x > mouseStartPosition.x + dragPadding)
        {
            向右移动();
            x位置=10;
        }
        别的
        {
            x位置=0;
        }
        如果 (mouseCurrentPosition.y > mouseStartPosition.y + dragPadding)
        {
            向上移动();
            y位置=10;
        }
        否则,如果(mouseCurrentPosition.y < mouseStartPosition.y - dragPadding)
        {
            向下移动();
            y位置=-10;
        }
        别的
        {
            y位置=0;
        }
        theStick.anchoredPosition = new Vector2(xPos, yPos);
    }
  12. Now, let’s create a function that checks how far the player has dragged their finger and moves the Stick based on that information. Add the following function to your script:
    public void Dragging()
    {
        float xPos;
        float yPos;
        mouseCurrentPosition = Input.mousePosition;
        if (mouseCurrentPosition.x < mouseStartPosition.x - dragPadding)
        {
            MovingLeft();
            xPos = -10;
        }
        else if (mouseCurrentPosition.x > mouseStartPosition.x + dragPadding)
        {
            MovingRight();
            xPos = 10;
        }
        else
        {
            xPos = 0;
        }
        if (mouseCurrentPosition.y > mouseStartPosition.y + dragPadding)
        {
            MovingUp();
            yPos = 10;
        }
        else if (mouseCurrentPosition.y < mouseStartPosition.y - dragPadding)
        {
            MovingDown();
            yPos = -10;
        }
        else
        {
            yPos = 0;
        }
        theStick.anchoredPosition = new Vector2(xPos, yPos);
    }
  13. 我们需要添加的最后一部分是当玩家停止拖动或抬起手指。将以下函数添加到脚本中即可实现此目的:
    公共无效StoppedDrag()
    {
        theStick.anchoredPosition = Vector2.zero;
    }
  14. The last piece we need to add is something that will reset the stick to its original position once the player stops dragging or lifts up their finger. Add the following function to your script to do so:
    public void StoppedDrag()
    {
        theStick.anchoredPosition = Vector2.zero;
    }
  15. 现在,我们需要将此脚本和这些函数挂接到场景中的项目。将FloatingAnalogStick脚本添加到Stick Base Image。
  16. Now, we need to hook this script and these functions to the items within the scene. Add the FloatingAnalogStick script to the Stick Base Image.
  17. 将Stick Image添加到Floating Analog Stick组件中的Stick属性
  18. Add the Stick Image to the Stick property in the Floating Analog Stick component.
  19. 使用添加组件|事件|事件触发器将事件触发器组件添加到Stick Base对象。这将允许用户使用除On Click()之外的事件类型。
  20. Add an Event Trigger component to the Stick Base object with Add Component | Events | Event Trigger. This will allow the user to use Event Types other than On Click().
  21. 使用添加新事件类型按钮添加开始拖动拖动结束拖动事件类型
  22. Add the Begin Drag, Drag, and End Drag Event Types with the Add New Event Type button.
  23. 将附加在Stick Base上的FloatingAnalogStick脚本上的相应函数添加到事件中:
  24. Add the appropriate functions on the FloatingAnalogStick script attached to the Stick Base to the Events:
图 11.40:事件触发器组件

图 11.40:事件触发器组件

Figure 11.40: The Event Trigger component

如果你现在玩游戏,你应该看到八向摇杆做出适当的反应。点击它,向任意方向拖动都会使操纵杆向方向移动阻力

If you play the game now, you should see the eight-directional analog stick responding appropriately. Clicking on it and dragging in any direction will cause the stick to move in the direction of the drag.

让八向虚拟摇杆悬浮起来

Making the eight-directional virtual analog stick float

如果您想要的只是一个八向模拟摇杆,那就太好了!但是,如果您想让模拟摇杆浮动(出现在玩家按下屏幕的位置,并在玩家抬起手指时消失),您就需要做更多的工作

If all you want is an eight-directional analog stick, you’re good to go! But if you want the analog stick to float—appear where the players press on the screen and disappear when they lift their finger—you have to do a little bit more work.

要使模拟摇杆出现在玩家点击的地方,完成以下步骤:

To make the analog stick appear where the player clicks, complete the following steps:

  1. 首先,我们需要创建一个区域,玩家点击该区域即可调出模拟摇杆。右键单击层次结构中的Canvas并选择UI | Button以向Canvas添加一个Button子项。将Button子项重命名为Click Area
  2. First, we need to create an area where the player will click to bring up the analog stick. Right-click Canvas in the Hierarchy and select UI | Button to add a Button child to the Canvas. Rename the Button child Click Area.
  3. 从点击区域中删除文本子对象
  4. Remove the Text child object from the Click Area.
  5. 拉伸点击区域以填充整个画布
  6. Stretch the Click Area to fill the whole Canvas.
  7. 通过将Rect Transform组件上的LeftTopRightBottom属性更改为50 ,为Click Area的两侧添加一些填充。我添加了此填充,以便玩家无法点击屏幕的边缘,并且模拟摇杆大部分出现在屏幕外。
  8. Add some padding to the sides of Click Area by changing the Left, Top, Right, and Bottom properties on the Rect Transform component to 50. I have added this padding so that the player cannot click on the very edge of the screen and have the analog stick appear mostly off-screen.
  9. 在 Hierarchy 中,将Click Area移动到Stick Base上方。现在,Click Area将渲染模拟摇杆后面
  10. In the Hierarchy, move Click Area so that it is above Stick Base. Now, Click Area will render behind the analog stick:
图 11.41:显示点击区域和摇杆底座的层次结构

图 11.41:显示点击区域和摇杆底座的层次结构

Figure 11.41: The Hierarchy showing Click Area and Stick Base

  1. 打开您的FloatingAnalogStick代码,以便我们可以为其添加一些功能。
  2. Open up your FloatingAnalogStick code so that we can add some functionality to it.
  3. 为了使我们的摇杆位置更容易与屏幕上鼠标的位置挂钩,我们应该移动摇杆底座,使其位于Canvas的左下角。将锚点和位置设置为左下角 锚点预设。
  4. To make the position of our analog stick more easily hook to the position of the mouse on the screen, we should move our stick base so that it is centered at the lower-left corner of the Canvas. Set the anchor and position to the bottom left anchor preset.
  5. 现在,将XY Pivot属性设置0.5
  6. Now, set the X and Y Pivot properties to 0.5.
  7. 将Pos XPos Y属性设置为0。这应该将摇杆放置在 Canvas(或屏幕)的左下角,并将其枢轴点设置为其中心:
  8. Set the Pos X and Pos Y properties to 0. This should place the analog stick at the lower-left corner of the Canvas (or screen) with its pivot point set to its center:
图 11.42:显示点击区域和摇杆底座的层次结构

图 11.42:显示点击区域和摇杆底座的层次结构

Figure 11.42: The Hierarchy showing Click Area and Stick Base

  1. 现在,我们需要另外两个变量来让摇杆出现在场景中我们想要的位置。添加如果已添加以下变量,则将以下变量添加到脚本中以分配摇杆和轨迹
    [SerializeField] 私有 RectTransform theBase;
    [SerializeField] private bool stickAdded = false;
  2. We need two more variables now to get the analog stick to appear where we want it to in the scene. Add the following variable to your script to assign the stick and track if it has been added:
    [SerializeField] private RectTransform theBase;
    [SerializeField] private bool stickAdded = false;
  3. 现在,创建以下函数将棍子添加到场景中:
    公共无效AddTheStick()
    {
        基准点.锚定位置 = 输入.鼠标位置;
        theStick.anchoredPosition = Vector2.zero;
        鼠标起始位置 = 输入.鼠标位置;
        坚持添加 = 真;
    }
  4. Now, create the following function to add the stick to the scene:
    public void AddTheStick()
    {
        theBase.anchoredPosition = Input.mousePosition;
        theStick.anchoredPosition = Vector2.zero;
        mouseStartPosition = Input.mousePosition;
        stickAdded = true;
    }
  5. 添加以下Update()函数来确定棍棒何时出现:
    无效更新()
    {
        如果 (stickAdded == true)
        {
            拖拽();
            如果(输入.GetMouseButtonUp(0))
            {
                // ToggleBaseCanvasGroup(false); // 此行被注释掉,因为提供的代码中未定义 ToggleBaseCanvasGroup
                坚持添加 = 假;
                停止拖动();
            }
        }
    }
  6. Add the following Update() function to determine when the stick will appear:
    void Update()
    {
        if (stickAdded == true)
        {
            Dragging();
            if (Input.GetMouseButtonUp(0))
            {
                // ToggleBaseCanvasGroup(false);  // This line is commented out as ToggleBaseCanvasGroup is not defined in the provided code
                stickAdded = false;
                StoppedDrag();
            }
        }
    }
  7. 将Stick Base添加到The Base插槽:
  8. Add Stick Base to the The Base slot:
图 11.43:浮动模拟摇杆组件

图 11.43:浮动模拟摇杆组件

Figure 11.43: The Floating Analog Stick component

  1. 添加事件触发器组件点击区域
  2. Add the Event Trigger component to the Click Area.
  3. 将以下指针向下事件添加到点击区域
    图 11.44:事件触发器组件

    图 11.44:事件触发器组件

    现在玩游戏时,模拟摇杆会出现在您点击的位置并通过拖动移动。

  4. Add the following Pointer Down Event to the Click Area:

    Figure 11.44: The Event Trigger component

    Playing the game now will have the analog stick appear where you click and move around with your dragging.

  1. 现在,让我们让摇杆仅在玩家触摸屏幕时才可见。向Stick Base添加一个Canvas Group组件
  2. Now, let’s make it so that the analog stick is only visible when the player is touching the screen. Add a Canvas Group component to Stick Base.
  3. Alpha设置为0将 Interactable 设置false将 Blocks Raycast 设置 false
  4. Set Alpha to 0, Interactable to false, and Blocks Raycast to false.
  5. 将以下变量添加到FloatingAnalogStick脚本跟踪CanvasGroup
    私人CanvasGroup theBaseVisibility;
  6. Add the following variable to your FloatingAnalogStick script to keep track of the CanvasGroup:
    private CanvasGroup theBaseVisibility;
  7. 添加以下Awake()函数来初始化theBaseVisibility变量:
    无效唤醒()
    {
        theBaseVisibility = theBase.GetComponent<CanvasGroup>();
    }
  8. Add the following Awake() function to initialize the theBaseVisibility variable:
    void Awake()
    {
        theBaseVisibility = theBase.GetComponent<CanvasGroup>();
    }
  9. 创建一个名为ToggleBaseCanvasGroup()的新函数来打开或关闭CanvasGroup的属性
    公共无效ToggleBaseCanvasGroup(bool可见)
    {
        theBaseVisibility.alpha = Convert.ToInt32(可见);
        BaseVisibility.可交互 = 可见;
        BaseVisibility.blocksRaycasts = 可见;
    }
  10. Create a new function called ToggleBaseCanvasGroup() to toggle the CanvasGroup’s properties on and off:
    public void ToggleBaseCanvasGroup(bool visible)
    {
        theBaseVisibility.alpha = Convert.ToInt32(visible);
        theBaseVisibility.interactable = visible;
        theBaseVisibility.blocksRaycasts = visible;
    }
  11. 添加以下内容AddTheStick()函数打开CanvasGroup
    切换BaseCanvasGroup(真);
  12. Add the following to the AddTheStick() function to turn on the CanvasGroup:
    ToggleBaseCanvasGroup(true);
  13. Update()函数最内层的if语句中添加以下内容关闭CanvasGroup
    切换BaseCanvasGroup(false);
  14. Add the following to the innermost if statement within the Update() function to turn off the CanvasGroup:
    ToggleBaseCanvasGroup(false);

现在,当玩家按下时,模拟摇杆就会出现,并沿着手指的方向移动,而当玩家抬起手指时,模拟摇杆就会消失。

Now, the analog stick will appear when the player presses down, move in the direction of their finger, and disappear when the player lifts their finger.

概括

Summary

UI 图像是 Unity UI 系统的核心组件之一,操作它们对于创建视觉交互用户界面至关重要。本章总结了我们在前几章中学到的所有技能,让我们创建利用事件、按钮和图像的有趣界面。

UI Images are one of the core components of the Unity UI system and manipulating them is essential to creating visually interactive user interfaces. This chapter culminated all the skills we have learned in the preceding chapters by letting us create interesting interfaces that utilized Events, Buttons, and Images.

在下一章中,我们将研究如何创建遮罩和滚动视图,以便我们可以在Panel 容器中容纳更多的子对象。

In the next chapter, we’ll look at how to create masks and scroll views so that we can hold even more child objects within our Panel containers.

12

12

使用遮罩、滚动条和滚动视图

Using Masks, Scrollbars, and Scroll Views

我们已经学习了如何使 UI 的所有组件同时显示在屏幕上,但通常您会有一些位于屏幕外或菜单外的 UI 元素,在您导航到它们或显示它们之前不可见。

We’ve learned how to make UI that has all of its components visible on the screen at once, but often you will have UI elements that are off-screen or off-menu and not visible until you navigate to them or reveal them.

在本章中,我们将讨论以下主题:

In this chapter, we will discuss the following topics:

  • 如何使用遮罩隐藏部分UI 图像
  • How to use masks to hide portions of UI Images
  • 使用滚动条并通过代码访问其属性
  • Using Scrollbars and accessing their properties via code
  • 利用 UI Scroll View 创建可滚动菜单
  • Utilizing the UI Scroll View to create scrollable menus
  • 创建带有遮罩和滚动文本的设置菜单
  • Creating a settings menu with a mask and scrolling text

笔记

Note

本章中展示的所有示例都可以在代码包中提供的 Unity 项目中找到。它们可以在标记为Chapter12 的场景中找到。

All the examples shown in this chapter can be found within the Unity project provided in the code bundle. They can be found within the scene labeled Chapter12.

每个示例图像都有一个标题,注明场景中的示例编号。

Each example image has a caption stating the example number within the scene.

在场景中,每个示例都在其自己的画布上,并且某些画布已停用。要查看停用画布上的示例,只需在 Inspector 中选中画布名称旁边的复选框即可。每个画布还具有自己的事件系统。如果您一次激活多个画布,这将导致错误。

In the scene, each example is on its own Canvas, and some of the Canvases are deactivated. To view an example on a deactivated Canvas, simply select the checkbox next to the Canvas’ name in the Inspector. Each Canvas is also given its own Event System. This will cause errors if you have more than one Canvas activated at a time.

技术要求

Technical requirements

您可以在此处找到本章节的相关代码和资产文件https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2012

You can find the relevant codes and asset files of this chapter here: https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2012

使用蒙版

Using masks

口罩影响物体在其形状内的可见性。如果物体受到遮罩的影响,遮罩限制区域之外的任何部分都将不可见。遮罩的可见区域可以通过带有Mask组件的图像或带有Rect Mask 2D组件的 Rect Transform来确定

Masks affect the visibility of objects within their shape. If an object is affected by a mask, any part of it outside the mask’s restricted area will be invisible. The visible area of a mask can either be determined by an Image with the Mask component or a Rect Transform with the Rect Mask 2D component.

在 UI 中,蒙版可用于滚动菜单,因此菜单区域之外的项目将不可见。它还用于剪切图像。例如,下图显示了一只猫的图像被圆形蒙版剪切掉

With UI, masking can be used for scrolling menus, so items that exist outside of the menu’s area will not be visible. It is also used to cut out images. For example, the following image shows a cat’s image being cut out by a circular mask:

图 12.1:第 12 章场景中的圆形遮罩示例

图 12.1:第 12 章场景中的圆形遮罩示例

Figure 12.1: Circular Mask Example in the Chapter12 scene

您可能会注意到,带面具的猫的边缘看起来不太好。为避免这种情况,请确保使用具有适当图像分辨率的精灵,并在精灵的导入设置中尝试不同的过滤模式。

You may note that the edges of the cat with the mask don’t look great. To avoid this, ensure that you use sprites with appropriate image resolutions and try different filtering modes on the Sprite’s Import Settings.

Mask 组件

The Mask component

Mask组件​可以添加到任何具有图像组件的 UI 对象。如果将其添加到没有图像组件的 UI 对象,它将不起作用,因为它需要图像来确定受限区域。

The Mask component can be added to any UI object with an Image component. If it is added to a UI object that doesn’t have an Image component, it won’t function since it needs an Image to determine the restricted area.

可以通过在 Inspector 中选择Add Component | UI | Mask将Mask组件添加到 UI对象

The Mask component can be added to a UI object by selecting Add Component | UI | Mask within the Inspector:

图 12.2:Mask 组件

图 12.2:Mask 组件

Figure 12.2: The Mask component

包含Mask组件的 UI 对象的任何子对象的可见性将被限制在其Image组件上的Source Image的不透明区域内

Any children of the UI object containing the Mask component will then have their visibility restricted to only the area within the opaque area of the Source Image on its Image component.

下图中,名为Mask的对象是一个添加了Mask组件的 UI Image。如您所见,Panel 左侧的紫色三角形仅部分可见,而右侧的绿色三角形则完全可见。绿色三角形完全可见,因为它不是包含Mask组件的 UI Image 的子项

In the following image, the object named Mask is a UI Image with a Mask component added to it. As you can see, the purple triangle on the left of the Panel is only partially visible, while the green triangle on the right is fully visible. The green triangle is fully visible because it is not a child of the UI Image that contains a Mask component:

图 12.3:第 12 章场景中的 Mask 组件示例

图 12.3:第 12 章场景中的 Mask 组件示例

Figure 12.3: The Mask Component Example in the Chapter12 scene

您可以选择隐藏定义蒙版组件可见区域的源图像。如果取消选择“显示蒙版图形”,则父级的源图像将不可见。这一点很重要需要注意的是,更改源图像的不透明度不会影响Mask 组件的功能。

You have the option to hide the Source Image that defines the Mask component’s visibility area. If you deselect Show Mask Graphic, the parent’s Source Image will not be visible. It’s important to note that changing the opacity on the Source Image does not affect the Mask component’s functionality.

Rect Mask 2D 组件

Rect Mask 2D component

使用Mask组件允许您将可见区域限制为非矩形形状。但是,如果您想将可见区域限制为矩形形状,并且不想使用图像来限制可见区域,则可以使用Rect Mask 2D组件。

Using the Mask component allows you to restrict the visible area to a non-rectangular shape. However, if you want to restrict the visible area to a rectangular shape and don’t want to use an image to restrict the visible area, you can use a Rect Mask 2D component.

可以通过在Inspector中选择Add Component | UI | Rect Mask 2D将 Rect Mask 2D 组件添加到 UI Object 中,如下所示

The Rect Mask 2D component can be added to a UI Object by selecting Add Component | UI | Rect Mask 2D within the Inspector, as shown in the following:

图 12.4:Rect Mask 2D 组件

图 12.4:Rect Mask 2D 组件

Figure 12.4: A Rect Mask 2D component

请注意,该组件允许您调整填充 柔软度

Notice that the component allows you to adjust Padding and Softness.

当将Rect Mask 2D组件添加到 GameObject 时,其子对象的可见性将受到其 Rect Transform 形状的影响。父对象上不需要Image组件, Rect Mask 2D组件即可正常运行。

When a Rect Mask 2D component is added to a GameObject, the visibility of its children will be affected by the shape of its Rect Transform. An Image component is not required on the parent object for the Rect Mask 2D component to function properly.

在下文中图像,创建一个空的UI 游戏对象,并向其添加一个Rect Mask 2D组件。然后为其指定一个子 UI 图像。如您所见,三角形被 Rect Transform 区域遮盖:

In the following image, an Empty UI GameObject is created and a Rect Mask 2D component is added to it. It is then given a child UI Image. As you can see, the triangle is masked by the Rect Transform area:

图 12.5:第 12 章场景中的 Rect Mask 2D 组件示例

图 12.5:第 12 章场景中的 Rect Mask 2D 组件示例

Figure 12.5: A Rect Mask 2D Component Example in the Chapter12 scene

如果您想将蒙版应用于矩形,我强烈建议您使用Rect Mask 2D组件而不是标准Mask组件,因为它的性能更佳。

If you want to apply a mask to a rectangular shape, I highly recommend that you use the Rect Mask 2D component rather than the standard Mask component, as it is more performant.

需要注意的是,正如名称Rect Mask 2D所暗示的那样,此遮罩仅适用于 2D 对象。您可以https://docs.unity3d.com/Manual/script-RectMask2D.xhtml上阅读有关其限制的更多信息。

It’s important to note that, as implied by the name Rect Mask 2D, this mask will only work on 2D objects. You can read more about its limitations at https://docs.unity3d.com/Manual/script-RectMask2D.xhtml.

正如我之前所说,蒙版的一个常见用途是创建一个菜单,其中的对象溢出可见范围区域。创建这些类型的菜单需要滚动条和滚动视图,所以现在让我们看看这些组件。

As I stated earlier, one common use for a mask is to create a menu with objects that spill out of the visible area. Creating these types of menus requires scrollbars and scroll views, so let’s look at those components now.

实现 UI 滚动条

Implementing UI Scrollbars

UI滚动条对象允许用户沿路径拖动手柄。路径上手柄的位置会影响图像或对象在可用区域内的位置。

The UI Scrollbar object allows the user to drag a handle along a path. The position of the handle on the path affects the position of an image or object within a usable area.

如果您无法从上述描述中看出滚动条的含义,这里有一个更简单的上下文解释。它最常用于视频游戏中,菜单中包含大量信息,但可视区域小于所有信息所占的区域

If you’re having trouble seeing what a scroll bar is from the preceding description, here’s an easier explanation with context. It is most commonly used in video games with menus that have a lot of information within a viewable area that is smaller than the area that all the information takes up.

要创建 UI Scrollbar,请选择+ | UI | Scrollbar。默认情况下,UI Scrollbar 有一个名为Sliding Area 的子项。Sliding Area还有一个名为Handle的子项。

To create a UI Scrollbar, select + | UI | Scrollbar. By default, a UI Scrollbar has a child named Sliding Area. The Sliding Area also has a child named Handle.

Sliding Area子对象是一个空的游戏对象。其目的是确保其子对象Handle的位置和对齐正确。Handle是一个 UI Image。它代表Scrollbar的可交互区域。

The Sliding Area child is an empty GameObject. Its purpose is to ensure that its child, the Handle, is correctly positioned and aligned. The Handle is a UI Image. It represents the interactable area of the Scrollbar.

如果要更改滚动条背景和Handle的外观,则需要分别更改滚动条父级和Handle子级上的图像组件的源图像

If you want to change the appearance of the Scrollbar’s background and Handle, you need to change the Source Images of the Image components on the Scrollbar parent and Handle child, respectively.

滚动条组件

The Scrollbar component

父母Scrollbar 对象有一个Scrollbar组件。它具有可交互 UI 对象共有的所有属性,以及Scrollbars 独有的一些属性:

The parent Scrollbar object has a Scrollbar component. It has all the properties common to the interactable UI objects, along with a few that are exclusive to Scrollbars:

图 12.6:Scrollbar 组件的属性

图 12.6:Scrollbar 组件的属性

Figure 12.6: The properties of the Scrollbar component

滚动条的始终限制在01之间。滚动条用于移动需要比可视空间多出更多空间。因此,滚动条的位置应该很容易转换为百分比或0 1之间的值。

A Scrollbar’s Value is always restricted to being between 0 and 1. Scrollbars are used to move objects that take up more room than the viewable space. Due to this, the Scrollbar’s position should easily translate to a percentage or a value between 0 and 1.

Handle Rect属性分配显示手柄图像的对象的 Rect Transform。默认情况下,Handle的 Rect Transform 被分配给此属性。您会注意到Handle GameObject上的 Rect Transform 组件具有由 Scrollbar 驱动的一些值消息,因为Handle的位置受 Scrollbar 的影响。Scrollbar 的Handle的位置与Scrollbar组件的Value属性相关联

The Handle Rect property assigns the Rect Transform of the object that displays the handle’s image. By default, the Rect Transform of Handle is assigned to this property. You›ll note that the Rect Transform component on the Handle GameObject has the Some values driven by Scrollbar message, since the position of Handle is affected by the Scrollbar. The position of the Scrollbar’s Handle is tied to the Value property of the Scrollbar component.

图 12.7:Handle GameObject 上的 Rect Transform 组件

图 12.7:Handle GameObject 上的 Rect Transform 组件

Figure 12.7: The Rect Transform component on the Handle GameObject

Direction属性允许您选择滚动条的方向。可用选项与 Slider 相同,并相应地进行平移:从左到右从右到左从下到上从上到下

The Direction property allows you to select the orientation of the Scrollbar. The available options are the same as with a Slider and translate accordingly: Left To Right, Right To Left, Bottom To Top, and Top To Bottom.

Size属性决定了 Scrollbar 的Handle占Scrollbar滑动区域的百分比。它可以是01之间的任意浮点值。我建议你选择一个相对于滚动对象大小的值这样 Scrollbar 的移动会感觉更直观。这意味着可滚动区域越大,Scrollbar Handle就越小。

The Size property determines the percentage of the Scrollbar’s Sliding Area taken up by the Scrollbar’s Handle. This can be any float value from 0 to 1. I recommend that you choose a value relative to the size of the objects being scrolled so that the Scrollbar’s movement feels more intuitive. This means that the larger the scrollable area is, the smaller the Scrollbar Handle becomes.

如果您希望滚动条具有交错、离散的步进,并且不希望它具有连续受控的移动,则可以使用步进数属性。如果您希望滚动条将滚动对象移动到特定位置,则可以使用该属性。

The Number of Of Steps property is used if you want the Scrollbar to have staggered, discrete steps and don’t want it to have continuously controlled movement. This is used if you want your scrollbar to move the scrolling objects to specific locations.

通常,您会看到一个由点表示的滚动区域(例如,在 iOS 设备上,用于标识您正在查看哪个主屏幕的点)。这可以通过使用大于0的Number Of Steps属性来实现。将此值设置为0将允许连续控制移动而不是交错的离散步骤。

Often, you will see a scroll area represented by dots (like the dots that identify which home screen you are viewing on an iOS device). This can be achieved using a Number Of Steps property greater than 0. Setting this value to 0 will allow for continuously controlled movement rather than staggered discrete steps.

本章末尾的示例部分提供了创建连续和离散滚动条的示例

Examples of creating continuous and discrete Scrollbars are provided in the Examples section at the end of this chapter.

滚动条默认事件 – 值改变时(单个)

Scrollbar default event – On Value Changed (Single)

Scrollbar组件的默认事件是On Value Changed事件,如 Scrollbar 组件的On Value Changed (Single)部分所示。每当 Scrollbar 的Handle移动时,都会触发此事件。它可以接受浮点参数。

The Scrollbar component’s default event is the On Value Changed event, as seen in the On Value Changed (Single) section of the Scrollbar component. This event will trigger whenever the Scrollbar’s Handle is moved. It can accept a float argument.

当公共函数具有浮点参数时,它将在函数的“ On Value Changed (Single)”事件的下拉列表中出现两次:一次在“Static Parameters”列表中,另一次在“Dynamic float”列表中,如下面的屏幕截图所示

When a public function has a float parameter, it will appear twice within the function’s dropdown list of On Value Changed (Single) events: once within a Static Parameters list and again within the Dynamic float list, as shown in the following screenshot:

图 12.8:静态参数和动态浮动方法

图 12.8:静态参数和动态浮动方法

Figure 12.8: Static Parameters and Dynamic float methods

如果函数是从“静态参数”列表中选择后,将出现一个框,允许您在事件中输入浮点数作为参数。然后,事件将仅发送该框内的值。在以下屏幕截图中显示的示例中,将发送到ScrollbarWithParameter()函数的唯一值将0

If the function is selected from the Static Parameters list, a box will appear that allows you to enter a float as an argument within the event. The event will then only send the value within that box. In the example shown in the following screenshot, the only value that will ever be sent to the ScrollbarWithParameter() function will be 0.

图 12.9:检查器中的静态参数方法示例

图 12.9:检查中的静态参数方法示例或者

Figure 12.9: Example of a Static Parameters method in the Inspector

如果您想要将滚动条的值作为参数发送给具有参数的函数,那么必须从动态浮点列表中选择该函数。

If you want the Scrollbar’s value to be sent as an argument to a function that has a parameter, you must select the function from the Dynamic float list.

以下函数和图像表示第 12 章场景中的滚动条示例,它触发调用带参数和不带参数的函数的事件:

The following functions and image represent the Scrollbar example found in the Chapter12 scene that triggers events that call functions with and without parameters:

公共无效ScrollbarWithoutParameter(){
    Debug.Log("已改变");
}
公共无效ScrollbarWithParameter(浮点值){
    调试.日志(值);
}
public void ScrollbarWithoutParameter(){
    Debug.Log("changed");
}
public void ScrollbarWithParameter(float value){
    Debug.Log(value);
}

在下文中屏幕截图,第三个选项显示从动态浮点列表中选择的函数,并将Value属性的值作为参数发送给该函数:

In the following screenshot, the third option shows the function chosen from the Dynamic float list and will send the value of the Value property as an argument to the function:

图 12.10:第 12 章场景中的滚动条事件示例

图 12.10:第 12 章场景中的滚动条事件示例

Figure 12.10: Events on Scrollbar Example in the Chapter12 scene

现在我们已经回顾了如何在 Unity 中实现 UI 滚动条,我们来看看 UI滚动视图。

Now that we’ve reviewed how to implement a UI Scrollbar in Unity, let’s look at UI Scroll Views.

实现 UI 滚动视图

Implementing UI Scroll View

UI滚动视图对象创建一个可滚动区域以及两个子 UI 滚动条。可以使用滚动条、拖动滚动视图内的区域或使用鼠标上的滚轮来滚动可滚动区域。

The UI Scroll View object creates a scrollable area along with two child UI Scrollbars. The scrollable area can be scrolled using the scrollbars, by dragging the area within the Scroll View, or using the scroll wheel on the mouse.

要创建 UI 滚动视图,请选择+ | UI |滚动视图。默认情况下,UI 滚动视图有三个子项:ViewportScrollbar Horizo​​ntalScrollbar Vertical。Viewport对象还有一个名为Content 的子项。Scrollbar Horizo​​ntalScrollbar Vertical子项具有与默认 UI 滚动条相同的父子关系,如上一节所述。

To create a UI Scroll View, select + | UI | Scroll View. By default, a UI Scroll View has three children: Viewport, Scrollbar Horizontal, and Scrollbar Vertical. The Viewport object also has a child named Content. The Scrollbar Horizontal and Scrollbar Vertical children have the same parent/child relationship as default UI Scrollbars, as discussed in the previous section.

尽管 UI Scroll View默认带有两个Scrollbar子项,但您不必在Scroll View中使用这两个 Scrollbar 。事实上,您根本就不必使用 Scrollbar!有关更多详细信息,请参阅Scroll Rect 组件部分

Even though the UI Scroll View comes with two Scrollbar children by default, you don’t have to use both Scrollbars with your Scroll View. In fact, you don’t have to use Scrollbars at all! Refer to the Scroll Rect component section for further details.

Viewport子对象是带有Mask组件的UI Image 。默认情况下, ViewportMask组件的Show Mask Graphic属性已关闭。从以下屏幕截图中的 Rect Transform 中可以看到,Viewport将遮罩应用于Scroll View中的某个区域

The Viewport child object is a UI Image with a Mask component. The Mask component of Viewport has the Show Mask Graphic property turned off, by default. As you can see from the Rect Transform in the following screenshot, the Viewport applies a mask to an area within the Scroll View:

图 12.11:UI 滚动视图视口

Figure 12.11: A UI Scroll View Viewport

这将使滚动视图中的项目只能在定义的区域(滚动条之间)内查看。您无法更改视口的大多数Rect Transform属性,因为此区域的设置取决于您在其Scroll Rect组件中设置滚动视图的方式(有关更多详细信息,请参阅实现 UI 滚动条部分)。

This will make the items within the Scroll View only viewable within the defined area (between the Scrollbars). You cannot change most of the Rect Transform properties of the Viewport, because this area is set based on how you have the settings of your Scroll View set in its Scroll Rect component (refer to the Implementing UI Scrollbar section for more details).

Viewport的子项是一个名为Content 的Rect Transform。它将充当您希望放置在Scroll View内的所有项目的持有者。您可以将Content视为将在Scroll View内移动的东西。从下图中可以看出, Content的 Rect Transform大于Viewport定义的可视区域,因为Scroll View的目标是让项目超出可视区域。

The child of Viewport is an empty Rect Transform named Content. This will act as the holder of all the items you wish to place within Scroll View. You can think of the Content as the things that will be moving around within the Scroll View. As you can see from the following image, the Rect Transform of Content is larger than the viewable area defined by Viewport, since the objective of a Scroll View is to have items outside of the viewable area.

图 12.12:UI 滚动视图内容

图 12.12:UI 滚动视图内容

Figure 12.12: A UI Scroll View Content

要添加项目在Scroll View 中,您只需将子对象添加到Content对象即可。由于ContentViewport的子对象,因此其任何子对象也将受到Viewport上Mask组件的影响

To add items to the Scroll View, you simply add children to the Content object. Since Content is a child of Viewport, any of its children will also be affected by the Mask component on the Viewport.

以下示例显示了将四幅图像添加为Content的子项。Content还被赋予了Vertical Layout Group组件:

The following example shows four images added as children of Content. Content has also been given a Vertical Layout Group component:

图 12.13:第 13 章场景中的滚动视图示例

图 12.13:第 13 章场景中的滚动视图示例

Figure 12.13: A Scroll View example in the Chapter13 scene

在前面图像中,您可以看到,当从Viewport禁用Mask组件时,所有项目都可见。设置滚动视图时,我强烈建议您在Viewport上禁用Mask ,以便您可以看到所放置项目的总体布局,并在布置完所有项目后重新启用它。

In the preceding image, you can see that when the Mask component is disabled from the Viewport, all items are visible. When you are setting up your Scroll View, I highly recommend that you disable the Mask on the Viewport so that you can see the general layout of the items you are placing and re-enable it when you are done laying out all the items.

您还应该调整Content的 Rect Transform 区域以包含其所有子项,或者使用Content Size Fitter组件,以便更轻松地预测 Scroll View 的行为。如果 Rect Transform Content的区域未完全包含所有项,则滚动Scroll View时可能不会显示其可视区域之外的项

You should also adjust the Rect Transform area of Content to enclose all of its child items or use a Content Size Fitter component so that you can more easily predict the behavior of Scroll View. If the area of the Rect Transform Content does not fully encompass all the items, scrolling the Scroll View may not show the items outside of its viewable area.

滚动视图包含一个图像组件。如果要更改滚动视图的背景(封装所有内容的灰色矩形),请更改滚动视图图像组件的源图像。您可以通过调整滚动条水平滚动条垂直子项的外观来更改滚动条的外观,如实现 UI滚动条部分中所述

Scroll View contains an Image component. If you want to change the background of the Scroll View (the gray rectangle that encapsulates everything), change the Source Image of the Image component on the Scroll View. You can change the appearance of the Scrollbars by adjusting the appearance of the Scrollbar Horizontal and Scrollbar Vertical children, as described in the Implementing UI Scrollbar section.

现在我们了解 Scroll Rect 的用途和设置后,我们来看一下 Scroll Rect 组件的各个属性nent。

Now that we understand the intent and setup of a Scroll Rect, let’s look at the individual properties of the Scroll Rect component.

滚动矩形组件

Scroll Rect component

行为确定滚动视图通过 Scroll View 父对象上的 Scroll Rect 组件

The behavior of the Scroll View is determined by the Scroll Rect component on the Scroll View parent object:

图 12.14:Scroll Rect 组件

图 12.14:Scroll Rect 组件

Figure 12.14: The Scroll Rect component

请注意滚动Rect 组件不具备本章(以及之前章节)中所有其他 UI 组件所具有的InteractableTransitionNavigation属性!

Note that the Scroll Rect component doesn’t have the Interactable, Transition, or Navigation properties that all the other UI components in this chapter (and previous chapters) have!

我将稍微不按顺序讨论这些属性,以便于讨论。

I’ll discuss the properties slightly out of order to make them easier to discuss.

Content属性被分配了将要滚动的 UI 元素的 Rect Transform。使用 UI Scroll View 对象时,默认情况下将其设置为Content的 Rect Transform 组件。Viewport 属性被分配了 Rect Transform,它是分配给Content属性的项目的父级。使用 UI Scroll View 对象时,默认情况下将其设置为Viewport的 Rect Transform 组件

The Content property is assigned the Rect Transform of the UI element that will scroll. When using the UI Scroll View object, this is set to the Rect Transform component of Content by default. The Viewport property is assigned the Rect Transform that is the parent of the item assigned to the Content property. When using the UI Scroll View object, this is set to the Rect Transform component of Viewport by default.

如果你正在创建一个可滚动区域而不使用 UI Scroll View,则必须分配ViewportContent属性,并且分配给Viewport的 Rect Transform 必须是矩形分配给Content 的变换,以使Scroll Rect组件正常运行财产。

If you are creating a scrollable area without using the UI Scroll View, the Viewport and Content properties must be assigned and the Rect Transform assigned to Viewport must be a parent of the Rect Transform assigned to Content, for the Scroll Rect component to function property.

运动特性

Movement properties

有三个与滚动视图中的内容如何移动相关的属性。水平垂直属性分别启用和禁用水平和垂直方向的滚动。默认情况下,它们都是启用的。移动类型属性决定了滚动视图如何在其边界上移动。

There are three properties related to how the Content in the Scroll View will move. The Horizontal and Vertical properties enable and disable scrolling in the horizontal and vertical directions, respectively. By default, they are both enabled. The Movement Type property determines how the Scroll View moves at its boundaries.

有三种移动类型选项:不受限制弹性夹紧。这些移动类型仅影响可滚动区域对可滚动区域拖动和鼠标滚轮的反应方式。使用滚动条时,您不会注意到这三种移动类型之间的差异。当使用滚动条移动内容时,所有移动类型的行为都将与夹紧移动类型完全相同

There are three Movement Type options: Unrestricted, Elastic, and Clamped. These Movement Types only affect the way the scrollable area reacts to being dragged by the scrollable area and to the scroll wheel on the mouse. You will not note a difference between these three Movement Types when you are using the Scrollbars. All Movement Types will behave exactly like the Clamped Movement Type when the Scrollbars are used to move the Content.

当选择“无限制”作为移动类型时,玩家可以无限拖动可滚动区域,而不受内容的矩形变换的限制。弹性限制移动类型将在到达内容的矩形变换的边缘后停止移动内容。但是,当选择“无限制”作为移动类型时,玩家可以继续拖动或使用滚轮。如果视口上有遮罩,则可能导致玩家将所有内容拖到可视空间之外。但是,如果玩家使用滚动条移动内容,则它们将被限制在内容范围内

When Unrestricted is selected as the Movement Type the player can drag the scrollable area endlessly without restriction to the Rect Transform of Content. The Elastic and Clamped Movement Types will stop moving the Content once the edges of Rect Transform of Content have been reached. However, when Unrestricted is selected as a Movement Type, the player can continue to drag or use the scroll wheel. If there is a mask on the Viewport, this can result in the player dragging all content outside of the viewable space. If the player moves the content with the scrollbars, however, they will be restricted to the bounds of Content.

当选择“弹性”作为“移动类型”时,如果将内容拖过其边界,则一旦玩家停止拖动,内容就会弹回原位。如果使用滚轮,它也会弹回。选择“弹性”后,子属性“弹性”将变为可访问。“弹性”属性决定了弹回的强度。

When Elastic is selected as the Movement Type, if the Content is dragged past its boundary, it will bounce into place once the player stops dragging. This will also bounce if the scroll wheel is used. When Elastic is selected, the subproperty Elasticity becomes accessible. The Elasticity property determines the intensity of the bounce.

当选择“夹紧”作为移动类型时,内容将无法拖过其边界,也不会反弹会发生。

When Clamped is selected as the Movement Type, the Content will not be draggable past its boundary and no bounce will occur.

与滚动速度有关的属性

Properties concerning scrolling speed

选择“惯性”属性将使内容在玩家停止拖动后继续移动。惯性仅在滚动区域被拖动时才会显现,并且不会影响由滚动条或鼠标滚轮初始化的内容移动。选择“惯性”后, “减速率”子属性将变为可访问。“减速率”属性决定了玩家停止拖动后内容何时停止移动。减速率为0时,内容将在玩家停止拖动的瞬间停止移动,减速率为1 时则永远不会停止移动。默认情况下,“减速率”设置0.135

Selecting the Inertia property will make the Content continue to move after the player has stopped dragging. Inertia is only apparent when the scroll area is dragged and doesn’t affect Content movement initialized by the Scrollbars or mouse scroll wheel. When Inertia is selected, the Deceleration Rate subproperty becomes accessible. The Deceleration Rate property determines when the Content will stop moving after the player has ceased dragging. A Deceleration Rate of 0 will stop the Content the instant the player stops dragging, and a Deceleration Rate of 1 will never stop. By default, Deceleration Rate is set to 0.135.

滚动灵敏度属性决定了滚轮每次转动时内容移动的距离。数字越高,内容每次转动移动的距离越远,从而使其似乎移动得更快。

The Scroll Sensitivity property determines how far the Content will move with each turn of the scroll wheel. The higher the number, the further the content will move with a turn, making it appear to move more quickly.

如果要禁用滚动视图的鼠标滚轮,请设置Scroll Sensi活动度 0

If you want to disable the use of the mouse scroll wheel for the Scroll View, set Scroll Sensitivity to 0.

滚动条的属性

Properties of the Scrollbars

您可以设置属性用于指定水平和垂直滚动条分别作出反应的方式。水平滚动条垂直滚动条属性指定您希望分别使用的 UI 滚动条的滚动条组件。默认情况下,它们分别被指定为水平滚动条子项和垂直滚动条子项

You can set properties for the way your horizontal and vertical scrollbars react separately. The Horizontal Scrollbar and Vertical Scrollbar properties assign the Scrollbar components of the UI Scrollbars you wish to use for each. By default, these are assigned the Scrollbar Horizontal child and Scrollbar Vertical child, respectively.

如果您只想在滚动视图区域使用拖动并且不想要滚动条,您可以简单地将水平滚动条垂直滚动条属性设置为,或者从场景中删除水平滚动条垂直滚动条对象。

If you only want to use drag on the Scroll View area and don’t want scrollbars, you can simply set the Horizontal Scrollbar and Vertical Scrollbar properties to None or delete the Scrollbar Horizontal and Scrollbar Vertical objects from the scene.

在每个滚动条分配下,您可以设置相应滚动条的可见性间距属性

Under each Scrollbar assignment, you can set the Visibility and Spacing properties of the respective Scrollbar.

Visibility属性有三个选项:PermanentAuto HideAuto Hide And Expand Viewport。当Visibility属性选择Permanent时,如果允许其相应移动,则相应的滚动条将保持可见,即使不需要它。例如,如下面的屏幕截图所示,如果允许水平移动,并且水平滚动条Visibility设置为Permanent,则相应的滚动条将可见,即使不需要它(无法实现水平移动):

The Visibility property has three options: Permanent, Auto Hide, and Auto Hide And Expand Viewport. When Permanent is selected for the Visibility property, the respective Scrollbar will remain visible, even if it is not needed, if its corresponding movement is allowed. For example, as shown in the following screenshot, if Horizontal movement is allowed, and the Horizontal Scrollbar’s Visibility is set to Permanent, the respective scrollbar will be visible, even though it is not necessary (no horizontal movement can be achieved):

图 12.15:调整滚动矩形中的滚动条

图 12.15:调整滚动矩形中的滚动条

Figure 12.15: Adjusting the Scrollbars in the Scroll Rect

然而,引用在同一张图片中,你可以看到,如果禁用了水平移动,则将水平滚动条可见性设置为永久可将其从滚动视图中完全移除。它在层次结构中也被禁用

However, referencing the same image, you can see that if Horizontal movement is deactivated, setting the Horizontal Scrollbar’s Visibility to Permanent removes it from the Scroll View entirely. It is also deactivated in the Hierarchy.

当为可见性属性选择了自动隐藏时,如果不需要相应的滚动条(意味着不需要在该方向上移动)或者禁用了相应的轴移动,则在游戏运行时,相应的滚动条将变得不可见并在层次结构中停用。

When Auto Hide is selected for the Visibility property, the respective Scrollbar will become invisible and deactivate in the Hierarchy when the game is played if it is not needed (meaning that there is no movement in that direction required) or if the respective axis movement is disabled.

Auto Hide And Expand Viewport属性的工作方式与Auto Hide相同,但它还会扩展分配给Viewport 的Rect Transform 的区域。如果Viewport对象具有Mask组件,这将导致 mask 的区域扩展以覆盖最初由Scrollbar占用的区域。

The Auto Hide And Expand Viewport property works in the same way as Auto Hide, but it will also expand the area of the Rect Transform assigned to Viewport. If the Viewport object has a Mask component, this will cause the mask’s area to expand to cover the area that was initially being taken up by the Scrollbar.

Spacing属性决定了Viewport的 Rect Transform 和Scrollbar的 Rect Transform之间的间距。默认情况下,此值设置为-3,这意味着两个 Rect Transform 略有重叠。如果要更改 Viewport 的位置则必须使用此属性,因为与Viewport的位置相关的属性在其 Rect Transf中被禁用orm 组件。

The Spacing property determines the space between the Viewport’s Rect Transform and the Scrollbar’s Rect Transform. By default, this value is set to -3, which means the two Rect Transforms overlap slightly. If you want to change the position of the Viewport, you have to do so with this property, since the properties related to the Viewport’s position are disabled in its Rect Transform component.

滚动矩形默认事件 – 值改变时(Vector2)

Scroll Rect default event – On Value Changed (Vector2)

滚动矩形组件的默认事件是On Value Changed事件,如Scroll Rect 组件的On Value Changed (Vector2)部分所示。每当通过拖动、使用鼠标滚轮滚动或使用滚动条之一滚动移动滚动视图内容区域时,都会触发此事件。它接受Vector2位置作为参数,并且与本章中讨论的其他事件一样,您可以选择不传递参数、传递静态参数或传递动态参数。

The Scroll Rect component’s default event is the On Value Changed event, as seen in the On Value Changed (Vector2) section of the Scroll Rect component. This event will trigger whenever the Content area of the Scroll View is moved by dragging, scrolling with the mouse scroll wheel, or scrolling with one of the Scrollbars. It accepts a Vector2 position as an argument and, as with the other events discussed in this chapter, you can choose to pass no argument, a static argument, or a dynamic argument.

如果要将ContentVector2位置发送给函数,则可以使用Dynamic Vector2列表中的Vector2参数将其发送给函数。发送的位置本质上是一个百分比,起始位置在相应坐标中的值为1.0 ,最后一个位置在相应坐标中的值为0.0

If you want to send the Vector2 position of Content to a function, you’d send it to a function with a Vector2 parameter from the Dynamic Vector2 list. The position sent is essentially a percentage, with the starting position having a value of 1.0 in the corresponding coordinate and the last position having a value of 0.0 in the corresponding coordinate.

让我们考虑以下函数:

Let’s consider the following function:

公共无效ScrollViewWithParameter(Vector2值)
{
    调试.日志(值);
}
public void ScrollViewWithParameter(Vector2 value)
{
    Debug.Log(value);
}

如果从动态 Vector2列表中选择了上一个函数,则下图显示了控制台中打印的Vector2

If the previous function is selected from the Dynamic Vector2 list, the following image shows the Vector2 values printed in the Console:

图 12.16:基于位置的滚动矩形的值

图 12.16:基于位置的滚动矩形的值

Figure 12.16: Values of the Scroll Rect based on position

现在我们已经了解了各种 UI 元素后,我们来看一些imp的示例警告他们。

Now that we’ve looked at the various UI elements, let’s look at some examples of implementing them.

示例

Examples

我之前提到过在本章中,遮罩和滚动视图的常见用法是包含多个项目的菜单。因此,在本章中,我们将仅创建一个示例,即来自现有菜单的滚动视图,通过模仿滚动的布局查看UI 对象。

I mentioned earlier in this chapter that a common usage of masks and scroll views is a menu with multiple items in it. So, for this chapter, we’ll only create one example, a scroll view from a pre-existing menu, by mimicking the layout of the Scroll View UI object.

从现有菜单创建滚动视图

Making a scroll view from a pre-existing menu

帮助组织项目中,复制上一章中创建的Chapter11-Examples场景;将其重命名为Chapter12-Examples。打开Chapter12-Examples并在该场景中完成以下示例。

To help organize the project, duplicate the Chapter11-Examples scene that you created in the last chapter; rename it Chapter12-Examples. Open Chapter12-Examples and complete the following example within that scene.

我希望能够向我的库存面板添加更多物品,并允许玩家滚动浏览这些物品。我可以创建一个新的 UI 滚动视图项目并将其更新为看起来像我当前的库存面板,但这样做会比它的价值更麻烦。因此,我将把当前的库存面板转换为可滚动视图。完成后,它将看起来像下图:

I want to be able to add more items to my Inventory Panel and allow the player to scroll through the items. I could create a new UI Scroll View item and update it to look like my current Inventory Panel, but that would be more hassle than it’s worth. So, instead, I will convert the current Inventory Panel to a scrollable view. After it is complete, it will look like the following figure:

图 12.17:我们将要构建的可滚动菜单

图 12.17:我们将要构建的可滚动菜单

Figure 12.17: The scrollable menu we will build

可以通过拖动食物旁边的区域来调整此面板的视图。我本来可以添加垂直滚动条来控制移动,但我想向您展示在没有滚动条的情况下制作可拖动区域是多么简单。而且没有滚动条看起来会更好看一些。

This Panel can have its view adjusted by dragging the area beside the food items. I could have added a vertical scrollbar to control the movement, but I wanted to show you how simple it is to make a draggable area without one. It also just looks a little nicer without a scrollbar.

要使库存面板可滚动,请完成以下步骤:

To make the Inventory Panel scrollable, complete the following steps:

  1. 现在,暂停面板很碍事。让我们通过取消选中其检查器中的复选框来禁用它,以便我们能够更轻松地查看库存面板
  2. Right now, the Pause Panel is in the way. Let’s disable it by deselecting the checkbox in its Inspector so that we can see the Inventory Panel more easily.
  3. 对于可滚动的视图要真正发挥作用,我们需要库存中有更多的物品。选择名为Item Holder的Inventory Holder的所有子项,然后使用Ctrl + D复制它们。现在,选择所有重复项,将它们重命名为Item Holder ,这样我们就不会在每个重复项上添加数字后缀。现在,您的Inventory Holder中的物品数量应该是原来的两倍,并且您应该看到以下内容:
  4. For the scrollable view to really work, we need more items in our inventory. Select all the children of Inventory Holder named Item Holder and duplicate them with Ctrl + D. Now, with all the duplicates selected, rename them Item Holder, so we don’t have the numbered suffix added to each of them. You should now have twice as many items in your Inventory Holder, and you should see the following:
图 12.18:步骤 2 后的结果

图 12.18:步骤 2 后的结果

Figure 12.18: The result after Step 2

  1. 获取可滚动的视图,我们需要向场景中添加更多项目,这些项目可以作为我们要滚动的内容的父级。您需要遵循非常具体的父/子关系,每个项目都有特定的组件来创建可滚动的视图,如下图所示
    图 12.19:用于创建可滚动菜单的层次结构布局

    图 12.19:用于创建可滚动菜单的层次结构布局

    让我们从将容纳滚动矩形组件的对象。选择库存面板,右键单击,然后选择创建空对象。这将创建一个空对象,它是我们的库存面板的子对象

  2. To get a scrollable view, we need to add a few more items to the scene that can be parents of the content we want to scroll. There is a very specific parent/child relationship you want to follow with each item having the specific components to create a scrollable view, as demonstrated in the following diagram:

    Figure 12.19: The Hierarchy layout to create a scrollable menu

    Let’s start with the object that will hold the Scroll Rect component. Select Inventory Panel, right-click, and select Create Empty. This will create an empty object that is a child of our Inventory Panel.

  1. 将新项目重命名为Scroll Rect并将其在Hierarchy中重新定位到Inventory Banner下方
  2. Rename the new item Scroll Rect and reposition it in the Hierarchy so that it is below Inventory Banner.
  3. 为Scroll Rect GameObject提供Scroll Rect组件,并在其 Inspector中添加 Component | UI | Scroll Rect
  4. Give the Scroll Rect GameObject the Scroll Rect component, with Add Component | UI | Scroll Rect in its Inspector.
  5. 现在,让我们创建将容纳Mask组件的项目。请记住,Mask组件必须位于也具有Image组件的对象上;因此,让我们创建一个 UI Image。右键单击Scroll Rect并选择UI | Image以创建Scroll Rect的子项
  6. Now, let’s create the item that will hold the Mask component. Remember that the Mask component must be on an object that also has an Image component; so, let’s create a UI Image. Right-click on Scroll Rect and select UI | Image to create a child of Scroll Rect.
  7. 重命名新的图像视口
  8. Rename the new Image Viewport.
  9. 库存支架现在在视觉上妨碍了视线,因此请暂时将其禁用。
  10. Inventory Holder is visually in the way right now, so disable it momentarily.
  11. 将uiElements_38图像添加到Viewport图像组件
  12. Add the uiElements_38 image to the Image component of Viewport.
  13. 调整Scroll Rect的 Rect Transform ,使其拉伸以填充其父级Rect转换。
  14. Adjust the Rect Transform of Scroll Rect so that it stretches to fill its parent’s Rect Transform.
图 12.20:Scroll Rect 对象的 Rect Transform

图 12.20:Scroll Rect 对象的 Rect Transform

Figure 12.20: The Rect Transform of the Scroll Rect object

  1. 调整Viewport的 Rect Transform 属性,如下所示:
  2. Adjust the Rect Transform properties of Viewport, as follows:
图 12.21:Scroll Rect 对象的 Rect Transform

图 12.21:Scroll Rect 对象的 Rect Transform

Figure 12.21: The Rect Transform of the Scroll Rect object

  1. 通过Add Component | UI | Mask为Viewport添加Mask组件
  2. Give Viewport the Mask component, with Add Component | UI | Mask.
  3. 重新启用库存持有者其重命名为Content
  4. Re-enable Inventory Holder and rename it Content.
  5. 将Content的Color属性的 alpha 设置为0,这样背景就不可见了。您应该看到以下内容:
  6. Set the alpha of the Color property on Content to 0 so that the background is invisible. You should see the following:
图 12.22:步骤 11 的结果

图 12.22:步骤 11 的结果

Figure 12.22: The results of Step 11

  1. 现在,将内容拖入Hierarchy使其成为Viewport的子项。这样做应该使遮罩立即应用于Content 的所有子项
  2. Now, drag Content in the Hierarchy so that it is a child of Viewport. Doing so should make the mask instantly apply to all of the children of Content:
图 12.23:步骤 12 的结果

图 12.23:步骤 12 的结果

Figure 12.23: The results of Step 12

  1. 定位内容,以便列表顶部的食物完全出现在面罩区域内:
  2. Position Content so that the food at the top of the list appears fully within the Mask area:
图 12.24:如何定位内容

图 12.24:如何定位内容

Figure 12.24: How to position the Content

  1. 现在,所有这些都是剩下要做的就是拥有一个正常运行的可滚动区域,以便设置Scroll Rect组件上的属性。选择Scroll Rect并将ContentViewport拖到Scroll Rect组件上的相应插槽中
  2. Now, all that is left to do is to have a properly functioning scrollable area to set up the properties on the Scroll Rect component. Select Scroll Rect and drag Content and Viewport into their appropriate slots on the Scroll Rect component.
  3. 禁用水平移动,因为我们只希望菜单垂直移动。将移动类型设置为“夹紧”,这样菜单就不会拉伸和弹跳,并保持在适当的范围内。您的Scroll Rect组件现在应如下所示:
  4. Disable Horizontal movement because we only want the menu to move vertically. Set Movement Type to Clamped so that the menu doesn’t stretch and bounce and stays within the appropriate bounds. Your Scroll Rect component should now look as follows:
图 12.25:分配内容和视口

图 12.25:分配内容和视口

Figure 12.25: Assigning the Content and Viewport

  1. 重新启用暂停菜单对象,以便我们的暂停菜单可以在游戏中发挥作用。
  2. Re-enable the Pause Menu object so that our Pause Menu will function in the game.

如果你玩游戏,库存面板现在应该有当你拖动它们时会滚动的物品。请记住,你可以通过按I键来调出库存面板键盘上的。由于我们在各个项目上具有拖放功能,因此要滚动,我们必须拖动没有上面的食物。

If you play the game, the Inventory Panel should now have items that scroll when you drag beside them. Remember that you bring up the Inventory Panel by pressing the I key on your keyboard. As we have drag and drop functionality on our individual items, to scroll, we have to drag the area of Content that does not have a food item on it.

概括

Summary

本章介绍了如何隐藏 UI 元素的可见性以及如何使用滚动视图创建容器,这些容器可容纳比屏幕一次可容纳的更多的项目。但是,还有相当多的可交互 UI 元素。在下一章中,我们将介绍 Unity UI 系统提供的其余交互式 UI 元素。

This chapter covered how to hide the visibility of UI elements as well as use Scroll Views to create containers that hold more items than the screen can hold at one time. There are still quite a few more interactable UI elements, however. In the next chapter, we will look at the remaining interactive UI elements provided by the Unity UI system.

十三

13

其他可交互的 UI 组件

Other Interactable UI Components

最流行的可交互 UI 组件是按钮。但是,除了按钮之外,还有多种类型的可交互 UI 元素。如果您想想最近填写的在线表单,您可能已经与按钮、文本字段以及单选按钮或复选框进行了交互。虽然从技术上讲,所有这些可交互项目都可以使用 UI 按钮、UI 文本和一些自定义代码来开发,但您不必自己构建它们!Unity 在 uGUI 系统中包含了多个常用的可交互 UI 项目,既可以作为您可以编辑的游戏对象,也可以作为您可以添加到现有游戏对象的组件。

The most popular interactable UI components are Buttons. However, there are multiple types of interactable UI elements other than buttons. If you think of an online form you’ve filled out recently, you’ve probably interacted with buttons, text fields, and possibly a radio button or checkbox. While technically all of these interactable items can be developed with UI Buttons, UI Text, and some custom code, you don’t have to build them yourself! Unity has included, within the uGUI system, multiple commonly used interactable UI items both as GameObjects that you can edit and as components that you can add to pre-existing GameObjects.

本章将回顾 uGUI 系统附带的所有其他预构建 UI 项目。在阅读了按钮和文本章节后,您将熟悉这些对象的大部分属性,但每个可交互项目都有一些专属于该 UI 项目类型的属性,因此我们将重点介绍这些属性。

This chapter will review all the other pre-built UI items that come with the uGUI system. After having reviewed the chapters on buttons and text, most of these objects’ properties will be familiar to you, but each interactable item has a few properties exclusive to that UI item type, so we’ll focus on those properties.

在本章中,我们将讨论以下主题:

In this chapter, we will discuss the following topics:

  • 使用UI 切换
  • Using UI Toggles
  • 使用UI 滑块
  • Using UI Sliders
  • 使用 UI 下拉菜单和下拉菜单 – TextMeshPros
  • Using UI Dropdowns and Dropdown – TextMeshPros
  • 使用 UI 输入字段和输入字段 – TextMeshPros
  • Using UI Input Fields and Input Field – TextMeshPros
  • 创建带有图像的下拉菜单
  • Creating a dropdown menu with images

笔记

Note

本节中展示的所有示例都可以在代码包中提供的 Unity 项目中找到。它们可以在标记为Chapter13 的场景中找到

All the examples shown in this section can be found within the Unity project provided in the code bundle. They can be found within the scene labeled Chapter13.

每个示例图像都有一个标题,注明场景中的示例编号。

Each example image has a caption stating the example number within the scene.

在场景中,每个示例都在其自己的画布上,并且某些画布已停用。要查看停用画布上的示例,只需在 Inspector 中选中画布名称旁边的复选框即可。每个画布还具有自己的事件系统。如果您一次激活多个画布,这将导致错误。

In the scene, each example is on its own Canvas, and some of the Canvases are deactivated. To view an example on a deactivated Canvas, simply select the checkbox next to the Canvas’ name in the Inspector. Each Canvas is also given its own Event System. This will cause errors if you have more than one Canvas activated at a time.

技术要求

Technical requirements

您可以在此处找到本章的代码: https: //github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2013

You can find the code for this chapter here: https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2013

使用 UI 切换

Using UI Toggle

UI Toggle对象是一个带有标签的可交互复选框。要创建 UI Toggle,请选择+ | UI | Toggle

The UI Toggle object is an interactable checkbox with a label. To create a UI Toggle, select + | UI | Toggle.

图 13.1:UI Toggle GameObject 和子对象

图 13.1:UI Toggle GameObject 和子对象

Figure 13.1: UI Toggle GameObject and children

默认情况下,UI 切换有两个子项:BackgroundLabelBackground还有一个子项,Checkmark

By default, a UI Toggle has two children: a Background and a Label. The Background also has a child, a Checkmark.

Background子项是一个UI Image,它表示Checkmark UI Image 出现的“框” 。Label是一个 UI Text 对象。

The Background child is a UI Image that represents the “box” in which the Checkmark UI Image appears. The Label is a UI Text object.

如果您想要改变框和复选标记的外观,您可以分别改变BackgroundCheckmark 子项上的 Image 组件的源图像。

If you want to change the appearance of the box and checkmark, you change the source images of the Image components on the Background and Checkmark children, respectively.

切换组件

Toggle component

父母Toggle 对象有一个Toggle组件。Toggle 组件看起来与 Button 组件非常相似,并且具有许多相同的属性。正如您将在本章中看到的那样,所有可交互 UI 对象的前几个属性都是相同的。组件底部的属性是UI Toggle 对象独有(图 13.2

The parent Toggle object has a Toggle component. The Toggle component looks very similar to the Button component and has many of the same properties. As you’ll see in this chapter, the first few properties of all interactable UI objects are the same. The properties at the bottom of the component are the ones that are exclusive to the UI Toggle object (Figure 13.2):

图 13.2:Toggle 组件的独特属性

图 13.2:Toggle 组件的独特属性

Figure 13.2: The Toggle component unique properties

Is On属性确定在场景中初始化时是否选中Toggle 。

The Is On property determines whether the Toggle is checked or not when it is initialized in the scene.

Toggle Transition属性决定了当切换按钮在打开和关闭之间或选中和未选中之间转换时会发生什么。两个选项是NoneFadeNone转换将立即在选中标记图像可见和不可见之间切换,而Fade转换将使选中标记图像淡入淡出

The Toggle Transition property determines what happens when the toggle transitions between on and off or checked and not checked. The two options are None and Fade. The None transition will instantaneously toggle between the checkmark Image being visible and not visible while the Fade transition will have the checkmark Image fade in and out.

Graphic属性指定将显示复选标记的图像组件。Checkmark 子项的图像组件会自动分配给此属性,但您可以根据需要更改它。

The Graphic property assigns the Image component that will display the checkmark. The Checkmark child’s Image component is automatically assigned to this property, but you can change it if you so desire.

最后一个属性Group指定Toggle Group组件,该组件将定义 Toggle 属于哪个 Toggle Group(如果有)。

The last property, Group, assigns the Toggle Group component that will define which Toggle Group the Toggle belongs to (if any).

Toggle组件的默认事件是“值改变时”事件,如“值改变布尔)”部分所示在。

The Toggle component’s default Event is the On Value Changed Event, as seen in the On Value Changed (Boolean) section.

切换默认事件 – 值改变时(布尔值)

Toggle default event – On Value Changed (Boolean)

Toggle组件的默认事件是On Value Changed事件,如Toggle 组件的 On Value Changed (Boolean) 部分所示每当选择或取消选择切换按钮时,都会触发事件。它可以接受布尔参数。

The Toggle component’s default event is the On Value Changed Event, as seen in the On Value Changed (Boolean) section of the Toggle component. This event will trigger whenever the toggle is selected or deselected. It can accept a Boolean argument.

当公共函数具有布尔参数时,它将在函数的“值改变(布尔)”事件下拉列表中出现两次:一次在静态参数列表中,另一次在动态布尔列表中,如下面的屏幕截图所示

When a public function has a Boolean parameter, it will appear twice within the function’s dropdown list of On Value Changed (Boolean) events: once within a Static Parameter list and again within the Dynamic bool list, as shown in the following screenshot:

图 13.3:ToggleWithParameter 方法的静态和动态版本

图 13.3:ToggleWithParameter 方法的静态和动态版本

Figure 13.3: The static and dynamic versions of the ToggleWithParameter method

如果从“静态参数”列表中选择了该函数,则复选框将作为事件中的参数出现。然后,事件将仅发送该复选标记的值。在上图所示的示例中,发送到 ToggleWithParameter ()函数的唯一值将是 false(因为复选框被取消选中)。

If the function is selected from the Static Parameters list, a checkbox will appear as an argument within the Event. The event will then only send the value of that checkmark. In the example shown in the preceding screenshot, the only value that will ever be sent to the ToggleWithParameter() function will be false (since the checkbox is deselected).

如果你想将Toggle 的.isOn值传递给脚本,必须从事件函数下拉列表中的动态布尔值(而非静态参数)列表中选择该函数。

If you want to pass the .isOn value of the Toggle to the script, the function must be chosen from the Dynamic bool (not Static Parameters) list in the function dropdown of the event.

为了演示“值改变(布尔)”事件的工作原理,让我们看看以下两个函数在调用时如何响应:

To demonstrate how On Value Changed (Boolean) events work, let’s see how the following two functions respond when called:

公共无效ToggleWithoutParameter(){
    Debug.Log("已改变");
}
公共 void ToggleWithParameter(bool 值){
    调试.日志(值);
}
public void ToggleWithoutParameter(){
    Debug.Log("changed");
}
public void ToggleWithParameter(bool value){
    Debug.Log(value);
}

在第 13 章场景中的 Toggle 中添加了以下事件

The following events are added to a Toggle in the Chapter13 scene:

图 13.4:第 13 章场景中的 Toggle 事件示例中的事件

图 13.4:第 13 章场景中的 Toggle 事件示例中的事件

Figure 13.4: Events on the Toggle Event Example in the Chapter13 scene

当取消选择场景内的 Toggle 时,控制台中将打印以下内容

When the Toggle within the scene is deselected, the following will print in the Console:

已更改
错误的
错误的
changed
False
False

当选择切换时,控制台中将打印以下内容

When the Toggle is selected, the following will print in the Console:

已更改
错误的
真的
changed
False
True

由于第一个事件调用的函数没有一个参数,它总是在Toggle的值改变的时候执行,不管执行的时候Toggle的值是什么。

Since the function called from the first event does not have a parameter, it will always execute when the value of the Toggle changes, regardless of what the value of the Toggle is when executed.

第二个事件将始终打印False的值,因为该函数有一个参数,并且由于该事件是从“静态参数”列表中选择的,因此发送的参数由复选框表示,该复选框设置为False。 因此,始终将值False发送给该函数。

The second event will always print the value of False, because the function has a parameter and, since the event was chosen from the Static Parameters list, the argument being sent is represented by the checkbox, which is set to False. So, the value False is always sent to the function.

第三个事件的函数是从动态布尔列表中选择的,因此发送给该函数的参数将是Toggle chan.isOn值ges。

The third event’s function was chosen from the Dynamic bool list, so the argument sent to the function will be the .isOn value to which the Toggle changes.

切换组组件

Toggle Group component

Toggle Group组件让你有许多 UI Toggle 协同工作,但一次只能选择或打开一个。当 Toggle 位于同一个 Toggle Group 中时,选择一个 Toggle 将关闭所有其他 Toggle。为了让 Toggle Group 在开始时正常工作,您应该将 Toggle group 内的所有 Toggle 的Is On属性设置为False,或者仅将单个 Toggle 的Is On属性设置True

The Toggle Group component allows you to have many UI Toggles that work together, where only one can be selected or on at a time. When Toggles are in the same Toggle Group, selecting one Toggle will turn off all others. For the Toggle Group to work properly at the start, you should either set all the Toggles within the Toggle group’s Is On property to False or set only a single Toggle’s Is On property to True.

Toggle Group组件不会创建可渲染的 UI 对象,因此将其附加到空的 GameObject 不会创建任何可见元素。

The Toggle Group component does not create a renderable UI object, so attaching it to an empty GameObject will not create any visible element.

一旦Toggle Group组件附加到 GameObject,就必须将其附加到的 GameObject 拖到内包含的每个 Toggle 的Group属性中:

Once the Toggle Group component is attached to a GameObject, the GameObject it is attached to must be dragged into the Group property of each of the Toggles that will be contained within the group:

图 13.5:Toggle Group 组件属性

图 13.5:Toggle Group 组件属性

Figure 13.5: The Toggle Group component properties

Toggle Group组件上只有一个属性:Allow Switch Off。Allow Switch Off属性允许在 Toggles 处于开启状态时将其关闭(如果已选择)。请记住,Toggle Group 组件一次最多强制开启一个 Toggles。因此,Allow Switch Off属性被关闭时,会强制始终至少选择一个 Toggles

There is only one property on the Toggle Group component: Allow Switch Off. The Allow Switch Off property allows the Toggles to be turned off if they are selected when already in the on state. Remember that the Toggle Group component forces at most one Toggle on at a time. So, the Allow Switch Off property being turned off forces there to be at least one Toggle selected at all times.

我的建议使用此组件时,需要使用一个空的 GameObject 作为要组合在一起的所有 Toggle 的父级。然后,此空的 GameObject 将包含 Toggle Group 组件(如第 13 章场景中的Toggle Group 示例中所示)。然后必须将包含 Toggle Group 组件的对象分配给每个Toggle 组件Toggle组件上的Group属性小孩。

My suggestion when using this component is to use an empty GameObject that acts as the parent for all the Toggles you wish to group together. This empty GameObject will then contain the Toggle Group component (as demonstrated in the Toggle Group Example in the Chapter13 scene). The object containing the Toggle Group component must then be assigned to the Group property on the Toggle component of each of the Toggle children.

UI 滑块

UI Slider

UI 滑块对象允许用户沿路径拖动手柄。路径上的位置对应于一系列值。

The UI Slider object allows the user to drag a handle along a path. The position on the path corresponds to a range of values.

创建 UI Slider,选择+ | UI | Slider。默认情况下,UI Slider 有三个子项:BackgroundFill AreaHandle Slide Area。Fill Area 也有一个子项 Fill,Handle Slide Area 有一个子项 Handle。

To create a UI Slider, select + | UI | Slider. By default, a UI Slider has three children: a Background, a Fill Area, and a Handle Slide Area. The Fill Area also has a child, Fill, and the Handle Slide Area has a child, Handle.

Background 子项是一个 UI Image,表示 Slider 的 Handle 可以遍历的整个区域。在默认的 Slider 示例中,这是被填充的深灰色背景区域。

The Background child is a UI Image that represents the full area that the Slider’s Handle can traverse. In the default Slider example, this is the darker gray background area that gets filled.

Fill Area 子项是一个空的游戏对象。其主要目的是确保其子项 Fill 正确对齐。Fill 是一个 UI 图像,它根据 Slider 的值延伸到 Fill Area。在默认的 Slider 示例中,这是拖在手柄后面并填充背景的浅灰色区域。

The Fill Area child is an empty GameObject. Its main purpose is to ensure that its child, the Fill, is correctly aligned. The Fill is a UI Image that stretches across the Fill Area based on the Slider’s value. In the default Slider example, this is the light gray area that trails behind the handle and fills in the Background.

Handle Slide Area 子对象也是一个空的游戏对象。它的目的是确保其子对象 Handle 的位置和对齐正确。Handle 也是一个 UI Image。Handle 代表Slider的可交互区域。

The Handle Slide Area child is also an empty GameObject. Its purpose is to ensure that its child, the Handle, is correctly positioned and aligned. The Handle is also a UI Image. The Handle represents the interactable area of the Slider.

如果要更改滑块的外观,可以更改背景、填充和手柄上图像组件的源图像孩子们。

If you want to change the appearance of the Slider, you change the Source Image of the Image components on the Background, Fill, and Handle children.

滑块组件

Slider component

父母Slider 对象有一个Slider组件。它具有所有可交互 UI 对象所共有的属性以及一些 Slider 独有的属性,如下图所示:

The parent Slider object has a Slider component. It has all the properties common to the interactable UI objects along with a few that are exclusive to Sliders, as highlighted in the following figure:

图 13.6:Slider 组件的独特属性

图 13.6:Slider 组件的独特属性

Figure 13.6: The unique properties of the Slider component

填充矩形属性分配对象的 Rect Transform显示填充区域的图像。默认情况下,这是 Fill GameObject 的 Transform 组件。您会注意到,在 Fill 的 Rect Transform 组件上,会显示一条消息,指出某些值由 Slider 驱动。这表示这些值是根据Slider组件更改的。在播放场景时,如果您移动 Slider 的 Handle,您将看不到 Fill 的 Rect Transform 属性更新。但是,如果您在Game视图中移动 Handle 时使 Scene 视图可见,您将看到 Fill 的 Rect Transform 区域随着您影响滑块而发生变化。

The Fill Rect property assigns the Rect Transform of the object that displays the Image of the filled area. By default, this is the Fill GameObject’s Transform component. You’ll note that on the Rect Transform component of the Fill, a message stating Some values driven by Slider is displayed. This indicates that the values are changed based on the Slider component. While playing the scene, if you move the Handle of the Slider, you will not see the Rect Transform properties of the Fill update. However, if you make the Scene view visible while moving the Handle in the Game view, you will see the Rect Transform area of the Fill change as you affect the slider.

Handle Rect属性分配显示手柄图像的对象的 Rect Transform。默认情况下,将 Handle 的 Rect Transform 分配给此属性。您会注意到,Handle GameObject 上的 Rect Transform 组件也具有由 Slider 驱动的一些值消息,因为 Handle 的位置受到Slider的影响。

The Handle Rect property assigns the Rect Transform of the object that displays the handle’s image. By default, the Rect Transform of Handle is assigned to this property. You’ll note that the Rect Transform component on the Handle GameObject also has the Some values driven by Slider message since the position of the Handle is affected by the Slider.

值的范围Slider 所表示的值由Min ValueMax Value属性决定。您可以为Min ValueMax Value属性分配任何值,甚至是负数。虽然Inspector允许您将Min Value定义为大于Max Value 的数字,但如果您这样做, Slider 将无法正常工作。

The range of values that the Slider represents is determined by the Min Value and Max Value properties. You can assign any value to the Min Value and Max Value properties, even negative numbers. While the Inspector allows you to define the Min Value as a number larger than the Max Value, the Slider will not work properly if you do so.

Direction属性允许您选择 Slider 的方向。可用选项包括从左到右从右到左从下到上从上到下。每个方向的位置顺序代表Slider 值范围的第一个位置(或最小值)和最后一个位置(或最大值)

The Direction property allows you to select the orientation of the Slider. The available options are Left To Right, Right To Left, Bottom To Top, and Top to Bottom. The order of the positions in each direction represents the first position (or Min Value) and then the last position (or Max Value) of the Slider’s value range.

如果选择了“整数”属性,则滑块可以表示的值的范围将被限制为整数(非十进制)值。

If the Whole Numbers property is selected, the range of values the Slider can represent will be restricted to integer (non-decimal) values.

笔记

Note

作为一名数学老师,我觉得有必要指出这一点。在数学中,术语“整数”代表所有非负整数(0 到无穷大)。在这里,在滑块组件中,“整数”一词代表所有整数,甚至负整数。因此,如果您和我一样是个数学迷,请不要让这暗示您如果选择了“整数”属性,滑块就不能容纳负值

As I am a math teacher, I feel the need to point this out. In math, the term Whole Numbers represents all non-negative Integers (0 through infinity). Here, in the Slider component, the term Whole Numbers represents all Integers, even negative ones. So, if you’re a math nerd like me, don’t let this imply to you that the Slider cannot hold negative values if the Whole Numbers property is selected.

Value属性是 Slider 的值。Slider 的 Handle 的位置与此属性相关。Inspector 中 Value 属性旁边的滑块Slide一对一表示在场景中。

The Value property is the value of the Slider. The position of the Slider’s Handle is tied to this property. The slider in the Inspector next to the Value property is a one-to-one representation of the Slider in the scene.

滑块默认事件 - 值改变时(单一)

Slider default event – On Value Changed (Single)

Slider 组件的默认事件是“值改变时”事件,如“值改变时(单个)”部分所示Slider 组件的。每当 Slider 的 Handle 移动时,都会触发此事件。它可以接受浮点参数。

The Slider component’s default event is the On Value Changed event, as seen in the On Value Changed (Single) section of the Slider component. This event will trigger whenever the Slider’s Handle is moved. It can accept a float argument.

如果您希望将 Slider 的值作为参数发送给具有参数的函数,则必须从动态浮点列表中选择该函数(类似于从 Toggle 的动态布尔列表中选择函数)。

If you want the Slider’s value to be sent as an argument to a function that has a parameter, you must select the function from the Dynamic float list (similar to selecting functions from the Toggle’s Dynamic bool list).

以下函数和屏幕截图代表在 Chapter7Text 场景中找到的 Slider 示例,它触发调用带参数和不带参数的函数的事件:

The following functions and screenshot represent a Slider example found in the Chapter7Text scene that triggers events that call functions with and without parameters:

public void SliderWithoutParameter(){
    Debug.Log("已改变");
}
public void SliderWithParameter(浮点值){
    调试.日志(值);
}
public void SliderWithoutParameter(){
    Debug.Log("changed");
}
public void SliderWithParameter(float value){
    Debug.Log(value);
}

在下面的屏幕截图中,第三个选项显示从动态浮点列表中选择的函数,并将Value属性的值作为参数发送给该函数:

In the following screenshot, the third option shows the function chosen from the Dynamic float list and will send the value of the Value property as an argument to the function:

图 13.7:第 13 章场景中的滑块事件示例

图 13.7:第 13 章场景中的滑块事件示例

Figure 13.7: Events on Slider Example in the Chapter13 Scene

笔记

Note

值得注意的是,如果选择了“整数”属性,并且滑块只能保存整数值,则此事件调用的函数将接收这些整数作为浮点值。

It’s important to note that if the Whole Numbers property is selected and the Slider can only hold integer values, the functions called by this event will receive those integers as float values.

现在我们已经回顾了如何使用滑块,让我们回顾一下如何使用两种类型的下拉菜单

Now that we’ve reviewed how to use Sliders, let’s review how to use the two types of Dropdowns.

UI 下拉菜单和下拉列表 – TextMeshPro

UI Dropdown and Dropdown – TextMeshPro

有两个可用的 Dropdown UI 对象,即 Unity 中打包的 UI Dropdown 对象和 Dropdown—TextMeshPro 对象。这两个 Dropdown 对象都允许用户从选项列表中进行选择。当 Dropdown被点击。从列表中选择一个对象后,列表将折叠,使所选选项在下拉菜单中可见(如果需要)。

There are two Dropdown UI objects available, the UI Dropdown object packaged in Unity and the Dropdown—TextMeshPro object. Both the Dropdown objects allow the user to select from a list of options. The list becomes visible when the Dropdown is clicked on. Once an object is selected from the list, the list will collapse, making the chosen option visible within the Dropdown (if desired).

这两个 Dropdown 选项的工作方式几乎完全相同。两者之间的唯一区别是 UI Dropdown 使用 UI Text 对象显示文本,而 Dropdown—TextMeshPro 使用 Text - TextMeshPro 对象。因此,我将在本节中同时讨论这两个对象。此外,由于这两个对象的功能相同,如果您想包含“花哨”的文本,则需要使用 Dropdown—TextMeshPro 而不是 UI Dropdown。

The two Dropdown options are pretty much identical in the way they work. The only difference between the two is the UI Dropdown uses UI Text objects to display text while the Dropdown—TextMeshPro uses Text - TextMeshPro objects. Due to this, I will discuss the two objects at the same time in this section. Additionally, because the two objects are identical in function, you will need to use Dropdown—TextMeshPro over the UI Dropdown if you want to include “fancy” text.

要创建 UI Dropdown,请选择+ | UI | Dropdown。要创建 Dropdown—TextMeshPro,请选择+ | UI | Dropdown - TextMeshPro 。如您在以下屏幕截图 (图 13.8 )中看到的,两个 Dropdown 对象具有相同的父/子对象关系和名称。默认情况下,Dropdown 对象有三个子对象:标签、箭头和模板。默认情况下,模板子对象处于禁用状态(因此,它在层次结构中显示为灰色),并且有多个子对象。

To create a UI Dropdown, select + | UI | Dropdown. To create a Dropdown—TextMeshPro, select + | UI | Dropdown - TextMeshPro. As you can see in the following screenshot (Figure 13.8), the two Dropdown objects have identical parent/child object relationships and names. By default, the Dropdown objects have three children: a Label, an Arrow, and a Template. The Template child is disabled by default (hence, it appears grayed out in the Hierarchy) and has multiple children.

本章下拉模板部分讨论了模板子项及其所有子项。

The Template child and all of its children are discussed in the Dropdown Template section of this chapter.

图 13.8:两种下拉菜单的层次结构

图 13.8:两种下拉菜单的层次结构

Figure 13.8: The Hierarchy of the two types of dropdowns

下列的段落中,我将讨论所有 Text 对象,就好像它们是 UI Text 对象一样。但是,请记住 Dropdown—TextMeshPro 使用 TextMeshPro - Text 对象。

In the following paragraphs, I will discuss all Text objects as if they are UI Text objects. However, remember that the Dropdown—TextMeshPro uses TextMeshPro - Text objects.

Label 子项是 UI Text 对象。默认情况下,它显示 Dropdown 对象中表示所选选项的文本。当玩家更改所选选项时, Label 的Text组件的Text属性将更改为相应的选项。要更改 Dropdown 的方框区域内显示的文本的属性,请更改Label 上的Text组件的属性。当新文本替换 Label 中的文本时,它将根据Label 的 Text 组件设置的属性自动显示。

The Label child is a UI Text object. By default, it displays the text within the Dropdown object that represents the selected option. As the player changes the selected option, the Text property of the Text component of Label changes to the appropriate option. To change the properties of the text that displays within the boxed area of the Dropdown, change the properties of the Text component on the Label. When new text replaces the text within the Label, it will automatically display based on the properties set by the Text component of the Label.

Arrow 子项是 UI Image。它的唯一功能是保存 Dropdown 右侧(默认情况下)显示的箭头的图像。此箭头实际上不执行任何操作,只是一张图片。它不接受输入,也不会随 Dropdown组件的属性而变化。

The Arrow child is a UI Image. Its only function is to hold the image for the arrow that (by default) appears at the right of the Dropdown. This arrow doesn’t actually do anything and is simply an image. It doesn’t accept inputs or change with the properties of the Dropdown component.

Dropdown 的背景图像位于主 Dropdown 父对象上,而不是名为 Background 的子对象上。因此,如果要更改 Dropdown 的背景和 Arrow 的外观,请分别更改Dropdown 父对象和 Arrow 子对象上的 Image 组件的源图像。Dropdown 的图像仅影响可以选择以显示下拉菜单的矩形。当玩家与下拉菜单交互时向外扩展的菜单背景由模板处理(将在下面的Dropdow中讨论)n模板部分)。

The background image of the Dropdown is on the main Dropdown parent object and not on a child named Background. Therefore, if you want to change the appearance of the Dropdown’s background and Arrow, you change the Source Images of the Image components on the Dropdown parent and Arrow child, respectively. The image of the Dropdown only affects the rectangle that can be selected to display the dropdown menu. The background to the menu that expands outward when the player interacts with the dropdown is handled by the Template (discussed in the following Dropdown Template section).

下拉模板

Dropdown Template

我们讨论了 Dropdown 组件的各种属性,让我们更仔细地看看Dropdown 的模板

Before we discuss the various properties of the Dropdown component, let’s look more closely at Dropdown’s Template.

的孩子名为“模板”的下拉菜单允许您设置下拉菜单中将作为选项出现的“项目”的属性。它还允许您设置菜单背景的属性以及当列表扩展超出下拉菜单的可视区域时出现的滚动条

The child of Dropdown named Template allows you to set the properties of the “items” that will appear as options in the dropdown menu. It also allows you to set the properties of the background of the menu and the Scrollbar that will appear if the list expands past the viewable area of the dropdown menu.

请记住,默认情况下模板子项是禁用的。启用模板(通过在其检查器中选中复选框)将在场景中显示模板。

Remember that the Template child is disabled by default. Enabling the Template (by selecting the checkbox in its Inspector) will display the Template in the scene.

图 13.9:启用下拉菜单的模板游戏对象

图 13.9:启用下拉菜单的模板游戏对象

Figure 13.9: Enabling the Template GameObject of the Dropdown

您可以在编辑器中永久启用此功能,因为一旦进入播放模式,它就会按预期隐藏

You can leave this permanently enabled in your Editor because once you enter Play mode, it will hide as it is supposed to.

如果仔细观察层次结构中的模板的父/子关系,您会注意到它只是一个带有一个滚动条的 UI 滚动视图对象:

If you look closely at the parent/child relationships of the Template within the Hierarchy, you’ll note that it is simply a UI Scroll View object with one Scrollbar:

图 13.10:下拉菜单中带有一个滚动条的 UI 滚动视图

图 13.10:下拉菜单中带有一个滚动条的 UI 滚动视图

Figure 13.10: The UI Scroll View with One Scrollbar within the Dropdown

查看Template GameObject 的Inspector显示,它实际上只是一个 UI Scroll View 对象,因为它附加了一个Scroll Rect组件没有指定水平滚动条

Viewing the Inspector of the Template GameObject, shows that it, in fact, is just a UI Scroll View object, as it has a Scroll Rect component attached to it with no Horizontal Scrollbar assigned:

图 13.11:模板的滚动矩形组件

图 13.11:模板的滚动矩形组件

Figure 13.11: The Scroll Rect component of the Template

的内容模板滚动视图对象有一个名为Item 的子项。Item有三个子项:Item BackgroundItem CheckmarkItem Label。如果你查看Item检查器,你会发现它只是一个 UI Toggle,并且具有与本章开头讨论的 UI Toggle 相同的子项和属性

The Content of the Template Scroll View object has a single child named Item. Item has three children: Item Background, Item Checkmark, and Item Label. If you look at the Inspector of Item, you’ll see that it is just a UI Toggle and has the same children and properties as the UI Toggles discussed at the beginning of this chapter.

因此,所有模板都是一个滚动视图,带有一个滚动条和一个切换按钮作为其内容!它最初看起来要复杂得多,但在你分解各个部分之后,你会意识到它只是我们已经讨论过的几个 UI 项目的组合

So, all Template is a Scroll View with a single Scrollbar and with a single Toggle as its Content! It looks way more complicated initially, but after you break down what the individual pieces are, you›ll realize that it›s just a combination of a few of the UI items we have already discussed!

图 13.12:模板子项的细分

图 13.12:模板子项的细分

Figure 13.12: Breakdown of the Template’s children

什么时候在职的使用下拉模板,如果您想更改视觉属性和设置,只需记住上图所示的细分,编辑它的前景就会变得没那么可怕。

When working with the Dropdown Template, if you want to change the visual properties and the settings, just remember the breakdown shown in the preceding figure, and the prospect of editing it will seem a lot less daunting.

您设置的下拉菜单中显示的每个项目选项都将遵循与您设置的选项完全相同的视觉属性项目切换 (Item Toggle) 进行设置。

Every item option you set to appear within the Dropdown will follow the exact same visual properties of those you set for the Item Toggle.

下拉列表组件

The Dropdown component

现在我们已经分解模板,我们可以看看Dropdown组件的属性

Now that we’ve broken down the Template, we can look at the properties of the Dropdown component.

父 Dropdown 对象有一个Dropdown(或 Dropdown - TextMeshPro)组件。它具有其他可交互 UI 对象共有的所有属性,以及Dropdown 独有的一些属性:

The parent Dropdown object has a Dropdown (or Dropdown - TextMeshPro) component. It has all the properties common to the other interactable UI objects, along with a few that are exclusive to Dropdowns:

图 13.13:两个 Dropdown 组件的区别

图 13.13:两个 Dropdown 组件的区别

Figure 13.13: The difference between the two Dropdown components

正如你可以看到从上图可以看出,UI Dropdown 和 Dropdown - TextMeshPro 的属性几乎完全相同。主要区别只有两个。首先,UI Dropdown 对象使用 UIText对象,而Dropdown-TextMeshPro对象使用Text-TextMeshPro对象。其次,Dropdown-TextMeshPro有一个Placeholder属性。

As you can see from the preceding image, the properties of the UI Dropdown and Dropdown - TextMeshPro are nearly identical. There are only two main differences. First, UI Dropdown objects use UI Text objects, while Dropdown - TextMeshPro objects use Text - TextMeshPro objects. Second, Dropdown - TextMeshPro has a Placeholder property.

Dropdown 组件实际上非常强大。它处理与下拉菜单的所有交互,并将切换文本显示、打开和关闭下拉菜单以及在下拉菜单内移动复选框。它甚至添加了滚动条和手柄,以允许下拉菜单拥有非常长的列表。您唯一需要编写的代码是如何解释玩家选择的属性。

The Dropdown component is actually super powerful. It handles all interactions with the Dropdown menu and will switch text displays, open and close the dropdown, and move around the checkbox within the dropdown. It even adds a scrollbar and handle to allow the dropdown menu to have a really long list. The only thing that must be coded by you is how to interpret the property the player selects.

让我们回顾一下f 两个Dropdown 对象。

Let’s review the various properties of the two Dropdown objects.

字幕属性

Caption properties

两个与标题或当前选定的选项相关的属性(图 13.3

There are two properties related to the caption or the option that is currently selected (Figure 13.3).

Caption Text属性引用 GameObject 的 Text 组件,该组件将显示当前选定选项的文本。默认情况下,这是 Label 子项的 Text 组件。Caption Text是可选的,因此如果您不希望当前选定的选项显示在下拉区域内(而只显示在下拉列表中),只需将Caption Text属性更改为None (Text)即可。

The Caption Text property references the Text component of the GameObject that will display the currently selected option’s text. By default, this is the Text component of the Label child. The Caption Text is optional, so if you do not want the currently selected option displayed within the Dropdown area (and only within the Dropdown list), simply change the Caption Text property to None (Text).

Caption Image属性保存 GameObject 的 Image 组件,该组件将显示当前选定选项的图像。默认情况下,此属性未分配任何内容,您会注意到 Dropdown没有可以保存 Image 的子项。要让图像与文本一起显示,您必须创建一个 UI Image 并将其分配给Caption Image属性。最好创建您创建的 UI Image作为Dropdown 的子项。

The Caption Image property holds the Image component of the GameObject that will display the currently selected option’s image. Nothing is assigned to this property by default and, you will note that the Dropdown does not have a child that can hold the Image. To have an image display with the text, you will have to create a UI Image and assign it to the Caption Image property. It is best that the UI Image you create is created as a child of the Dropdown.

模板属性

Template properties

有三个属性与将模板的属性分配给下拉列表中显示的所有可能选项相关。

There are three properties related to assigning the template’s properties to all possible options to display in the dropdown list.

Template属性引用模板的 Rect Transform。如前所述,模板定义下拉列表中每个选项外观以及下拉框的外观。默认情况下,此属性分配给子Template 对象。

The Template property references the Rect Transform of the template. As stated previously, the template defines the way each option within the dropdown list will look as well as how the dropdown holder will look. By default, this property is assigned to the child Template object.

Item Text属性引用GameObject 的Text组件,该组件包含项目模板的文本。默认情况下, Item Label(Item 的子项)上的Text组件被分配给此属性。

The Item Text property references the Text component of the GameObject that holds the text of the item template. By default, the Text component on the Item Label (child of the Item) is assigned to this property.

Item Image属性引用GameObject 的Image组件,该组件保存项目模板的图像。默认情况下,此属性未分配,类似于Caption Image 。与Caption Image一样,要使用此属性,需要创建一个 UI Image 并将其分配给此属性。如果创建一个,请确保将其添加为Templ 中Item的子项吃孩子以避免混淆。

The Item Image property references the Image component of the GameObject that holds the image of the item template. By default, this property is unassigned, similar to the Caption Image. Just as with the Caption Image, to use this property, a UI Image will need to be created and assigned to this property. If you create one, ensure that you add it as a child of Item within the Template child to avoid confusion.

选项属性

Option properties

Value属性​表示当前选择了哪个选项。选项位于列表中,Value属性中的数字表示当前所选选项在列表中的索引。由于选项由其索引表示,因此第一个选项的 Value0(而不是 1)。

The Value property represents which option is currently selected. The options are in a list, and the number in the Value property represents the currently selected option’s index within the list. Since the options are represented by their indices, the first option has a Value of 0 (not 1).

Options属性列出了下拉菜单中的所有选项。在列表中,每个选项都有一个文本字符串和精灵(可选)。此列表中的所有字符串和精灵将根据 Dropdown 组件的属性自动交换为 Dropdown 子项的正确组件属性因此,您无需编写任何代码来确保玩家与Dropdown 交互时这些项目能够正确显示。

The Options property lists out all the options within the Dropdown menu. Within the list, each option has a text string and sprite (optional). All strings and sprites within this list will automatically swap into the correct component properties of the children of Dropdown, based on the properties of the Dropdown component. So, you will not have to write any code to ensure that these items display appropriately when the player interacts with the Dropdown.

默认情况下,选项列表包含三个选项。但是,您可以通过选择列表底部的加号和减号来添加或减去选项。您还可以通过拖放选项的手柄(两条水平线)来重新排列列表中的选项。请注意,重新排列列表中的选项将更改它们在列表中的索引,然后更改它们的结束Dropdown组件。

By default, the Options list contains three options. However, you can add or subtract options by selecting the plus and minus sign at the bottom of the list. You can also rearrange the options within the list by dragging and dropping the options’ handles (two horizontal lines). Note that rearranging the options in the list will change their indices within the list and then change the Value they send to the Dropdown component.

下拉菜单默认事件 – 值改变时 (Int32)

Dropdown default event – On Value Changed (Int32)

下拉组件处理与下拉菜单本身的所有交互。您唯一需要编写的代码是如何解释玩家选择的选项。

The Dropdown component handles all interactions with the Dropdown menu itself. The only thing that has to be coded by you is how to interpret the option the player selected.

Dropdown 组件的默认事件是On Value Changed事件,如Dropdown组件的On Value Changed (Int32)部分所示。每当玩家选择新选项时,此事件都会触发。它接受整数作为参数,并且与本章讨论的其他事件一样,您可以选择不传递参数、传递静态参数或传递动态参数。

The Dropdown component’s default event is the On Value Changed Event, as seen in the On Value Changed (Int32) section of the Dropdown component. This event will trigger whenever a new option is selected by the player. It accepts an integer as an argument and, as with the other events discussed in this chapter, you can choose to pass no argument, a static argument, or a dynamic argument.

如果要将所选选项的索引(或Value属性的值)发送给函数,则可以使用Dynamic int列表中的 Int32 参数将其发送给函数。请参阅本文末尾的创建带图像的下拉菜单示例以了解此方法的实现。

If you want to send the index of the option selected (or the value of the Value property) to a function, you›d send it to a function with a Int32 parameter from the Dynamic int list. Refer to the Creating a dropdown menu with images example at the end of the text for an implementation of this.

我们将要介绍的下一个可交互 UI 组件l review 是 UI输入字段。

The next interactable UI component we’ll review is the UI Input Field.

UI 输入字段

UI Input Field

UI 输入字段提供玩家可以输入文本的空间

The UI Input Field provides a space in which the player can enter text.

要创建 UI 输入字段,请选择+ | UI |输入字段。默认情况下,InputField GameObject 具有两个子对象:一个占位符和一个文本对象。

To create a UI Input Field, select + | UI | Input Field. By default, the InputField GameObject has two children: a Placeholder and a Text object.

Placeholder 子项是一个 UI Text 对象,表示在玩家输入任何文本之前显示的文本。一旦玩家开始输入文本,Placeholder GameObject 上的 Text 组件就会停用,使文本不再可见。默认情况下,Placeholder 显示的文本是Enter text…,但显示的文本及其属性可以通过影响 Placeholder的Text组件上的属性轻松更改

The Placeholder child is a UI Text object that represents the text displayed before the player has input any text. Once the player begins entering text, the Text component on the Placeholder GameObject deactivates, making the text no longer visible. By default, the text displayed by the Placeholder is Enter text…, but the text being displayed as well as its properties are easily changed by affecting the properties on the Text component of the Placeholder.

Text 子项是一个 UI Text 对象,用于显示玩家输入的文本。设置 Text 对象的Text组件的属性将更改玩家输入的文本的显示。

The Text child is a UI Text object that displays the text the player inputs. Setting the properties on the Text object’s Text component will change the display of the text entered by the player.

InputField 包含一个Image组件。如果要更改输入框的外观,请更改IInputField上的mage组件。

InputField contains an Image component. If you want to change the appearance of the input box, change the Source Image of the Image component on the InputField.

输入字段组件

Input Field component

父 InputField 对象有一个输入字段组件。它具有可交互 UI 对象所共有的所有属性以及一些输入字段独有的属性:

The parent InputField object has an Input Field component. It has all the properties common to the interactable UI objects along with a few that are exclusive to Input Fields:

图 13.14:输入字段组件的独特属性

图 13.14:独特的属性输入字段组件的

Figure 13.14: The unique properties of the Input Field component

让我们看看 UI输入字段的各种属性。

Let’s look at the various properties of the UI Input Field.

输入文本和屏幕键盘的属性

Properties of entered text and onscreen keyboards

许多输入字段组件中的属性会影响输入字段中显示的文本。由于某些属性有很多选项和相关信息,我将按略微不按顺序进行讨论。

Many of the properties within the Input Field component affect the text that displays within the Input Field. Due to some of the properties having a lot of options and information pertaining to them, I will discuss them slightly out of order.

请记住,要更改输入文本的视觉样式,您需要更改Text GameObject上的Text组件的属性

Remember that to change the visual style of the entered text, you need to change the properties of the Text component on the Text GameObject.

Text Component 属性引用 GameObject 的 Text 组件,该组件将显示玩家输入的文本。默认情况下,这是Text子项的Text组件

The Text Component property references the Text component of the GameObject that will display the player’s entered text. By default, this is the Text component of the Text child.

Text属性是当前输入到输入字段中的文本。当您尝试从输入字段检索数据时,您希望从此属性获取信息,而不是从文本游戏对象上的文本组件获取信息。文本游戏对象上的文本组件将仅存储当前显示的内容。因此,如果文本由于是密码或已滚动而显示为星号,则完整且正确的文本将不会存储在文本游戏对象的文本组件中

The Text property is the text currently entered into the Input Field. When you are attempting to retrieve the data from the Input Field, you want to get the information from this property and not from the Text component on the Text GameObject. The Text component on the Text GameObject will only store what is currently being displayed. So, if the text is displayed as asterisks because it’s a password or has scrolled, the full and correct text will not be stored in the Text component of the Text GameObject.

字符限制属性允许您指定玩家可以在字段中输入的最大字符数。将“字符限制”属性设置为0可允许无限制的文本输入。

The Character Limit property allows you to specify the maximum number of characters the player can enter into the field. Leaving the Character Limit property set to 0 allows unlimited text entry.

占位属性引用GameObject 的Text组件,当玩家未输入任何内容或清除所有输入的文本时显示文本。默认情况下,这是Placeholder子项的Text组件

The Placeholder property references the Text component of the GameObject that displays the text when the player has either not entered anything or has cleared all entered text. By default, this is the Text component of the Placeholder child.

隐藏移动输入属性允许您覆盖选择文本输入字段时弹出的默认移动键盘。如果您有自己的键盘并希望播放器使用,则可选择此选项。目前,这仅适用于iOS 设备。

The Hide Mobile Input property allows you to override the default mobile keyboard that pops up when a text Input Field is selected. You will select this option if you have your own keyboard that you want the player to use. Currently, this only works for iOS devices.

如果您想在 Android 设备上使用自己的键盘,最好的办法是创建自己的自定义输入字段脚本。该脚本会在选择输入框时显示键盘,然后根据自定义键盘的按键更改框内的文本。

If you wanted to use your own keyboard on an Android device, your best bet would be to create your own custom input field script. The script would show a keyboard when the input box is selected and then change the text within the box based on the custom keyboard key presses.

只读属性使输入字段内的文本保持静态,玩家无法编辑。激活此属性后,玩家仍然可以选择文本进行复制和粘贴。

The Read Only property makes the text within the Input Field static and uneditable by the player. The player can still select the text to copy and paste it when this property is activated.

什么时候选择只读属性后,仍可以通过访问输入字段组件上的Text属性来通过代码编辑输入字段显示的文本。但是,更改文本 GameOb 的Text组件上的Text属性ject,不会改变显示的文本。

When the Read Only property is selected, the text displayed by the Input Field can still be edited via code by accessing the Text property on the Input Field component. However, changing the Text property on the Text component of the Text GameObject, will not change the displayed text.

内容类型

Content Types

内容类型属性确定输入字段将接受的字符类型。在屏幕上显示键盘的设备上,它还会影响选择输入字段时设备显示的键盘。如果所需的键盘不可用,则将显示默认键盘。例如,如果设备没有仅数字键盘,它将显示默认键盘。有关每个键盘和这些内容类型附带的字符验证的更详细说明,请参阅键盘类型字符验证选项部分。

The Content Type property determines the types of characters that will be accepted by the Input Field. On devices that display keyboards on screen, it also affects the keyboard that is displayed by the device when the input field is selected. If the desired keyboard is not available, the default keyboard will be displayed. For example, if the device does not have a numbers-only keyboard, it will display the default keyboard. For more detailed explanations of each keyboard and character validations that come with these Content Types, refer to the Keyboard Types and Character Validation Options sections.

可能的选项有标准自动更正、整数、十进制字母数字姓名电子邮件地址密码PIN自定义

The possible options are Standard, Autocorrected, Integer Number, Decimal Number, Alphanumeric, Name, Email Address, Password, Pin, and Custom.

标准选项允许输入任何字符。但请注意,输入的文本字体不支持的字符将不会显示。

The Standard option allows any character to be entered. Note, however, that characters not available for the entered text’s font will not display.

自动更正选项的工作方式与标准选项类似,但在带有屏幕键盘(尤其是触摸屏键盘)的设备上,它允许设备的自动更正功能根据其自身的自动更正算法自动覆盖单词。

The Autocorrected option works like the Standard option, but on devices with onscreen keyboards (particularly touchscreen keyboards), it allows the device’s autocorrect functionality to automatically override words based on its own autocorrecting algorithms.

整数选项仅允许整数值(无小数的正数和负数)。玩家将被限制输入多个负号。小数选项的工作原理类似,但它也接受小数点。玩家将被限制输入多个小数点。在带有屏幕键盘的设备(尤其是移动设备)上,这两个选项将显示数字键盘,而不是标准键盘

The Integer Number option allows only integer values (positive and negative numbers without decimals). The player will be restricted from entering more than one negative symbol. The Decimal Number option works similarly, except that it also accepts decimal points. The player will be restricted from entering more than one decimal point. On devices with onscreen keyboards (particularly mobile devices), the numeric keyboard will appear rather than the standard keyboard with these two options.

字母数字选项仅允许字母(大写和小写)以及数字和输入。不接受数学符号和标点符号,包括负数和小数点(句点)。

The Alphanumeric option only allows letters (uppercase and lowercase) along with numbers and input. Mathematical symbols and punctuation, including negative numbers and decimal points (periods), are not accepted.

名称选项将自动将字段中输入的每个新单词大写。玩家可以选择将单词的首字母小写,方法是删除该字母,然后以小写形式重新输入。

The Name option will automatically capitalize each new word entered within the field. The player has the option to lowercase the first letter of a word by deleting the letter and re-entering it in lowercase.

电子邮件地址选项将允许玩家输入电子邮件地址。它还将限制玩家输入多个 @ 符号或两个连续的句点(点/小数)。

The Email Address option will allow the player to enter an email address. It will also restrict the player from entering more than one @ symbol or two consecutive periods (dots/decimals).

密码选项允许在字段中输入字母、数字、空格和符号。当玩家在密码输入字段中输入文本时,输入的文本将隐藏起来并显示为星号 ( * )。

The Password option allows letters, numbers, spaces, and symbols entered in the field. When the player enters text into a Password Input Field, the entered text will be hidden from view and displayed as asterisks (*).

Pin选项仅允许输入整数(无小数)。负数也可以接受。玩家在Pin 内容类型字段中输入的文本将被隐藏,就像密码选项隐藏玩家输入一样。在屏幕键盘设备上,数字键盘将与Pin选项一起显示

The Pin option allows only integer numbers (no decimals) to be entered. Negative numbers are accepted. The text entered by the player in a field with the Pin Content Type will be hidden in the same way the Password option hides the player input. On an onscreen keyboard device, the numeric keyboard will be displayed with the Pin option.

最后一个选项“自定义”可让您最大程度地控制输入类型。选中后,检查器中会出现新属性,允许您选择线型输入类型e键盘类型字符验证

The final option, Custom, gives you the most control of the type of input. When selected, new properties appear in the Inspector allowing you to select the Line Type, Input Type, Keyboard Type, and Character Validation.

线型

Line Types

线型选项​适用于内容类型标准自动更正自定义选项。行类型选项有三种:单行多行提交多行换行。所有其他内容类型自动限制为单行类型。如果允许玩家输入的文本多于输入字段的可见区域可以显示的文本(即字符限制属性不将其限制在可见空间内),则文本将根据所选的类型滚动。

The Line Type option is available with the Standard, Autocorrect, and Custom options for Content Type. There are three Line Type options: Single Line, Multi Line Submit, and Multi Line New Line. All other Content Types are automatically restricted to Single Line types. If the player is allowed to enter more text than the Input Field’s visible area can display (meaning the Character Limit property does not restrict it to the visible space), the text will scroll based on the Line Type selected.

单行选项仅允许输入的文本显示在一行上。如果文本超出可见范围水平空间,文本将水平滚动。如果玩家按下Enter键,输入字段将表现为文本已提交。

The Single Line option only allows the entered text to be displayed on one line. If the text exceeds the visible horizontal space, the text will scroll horizontally. If the player hits the Enter key, the Input Field acts as if the text has been submitted.

多行提交多行换行选项允许文本在超出可见水平空间时垂直溢出,在超出可见垂直空间时垂直滚动。这两个选项的区别在于按下Enter键时会发生什么:多行提交将提交文本nd Multi Line New Line将开始新的一行。

The Multi Line Submit and Multi Line New Line options allow the text to overflow vertically if it exceeds the visible horizontal space and scroll vertically if the text exceeds the visible vertical space. The difference between the two options is what happens when the Enter key is hit: Multi Line Submit will submit the text and Multi Line New Line will start a new line.

输入类型

Input Types

选择自定义内容类型后,您可以从三种输入类型中进行选择:标准自动更正密码

When the Custom Content Type is selected, you have the option to select from three Input Types: Standard, Autocorrect, and Password.

选择这些不同的输入类型不会改变键盘或提供任何验证,就像名称类似的内容类型一样。例如,密码输入类型将接受Enter键作为多行新行的新行,并在字段中将其显示为星号,但在存储在Text属性中的实际数据中将其接受为新行

Selecting these various Input Types does not change the keyboard or provide any validation, like the similarly named Content Types. For example, the Password Input Type will accept the Enter key as a new line with Multi Line New Line and display it as an asterisk in the field but accept it as a new line in the actual data stored in the Text property.

标准选项不会对输入类型施加任何特殊情况。

The Standard option does not put any special circumstances on the type of input.

自动更正选项适用于具有内置自动更正功能的屏幕键盘平台。此选项允许设备的自动更正功能根据需要更改文本

The Autocorrect option applies to platforms with onscreen keyboards that have built-in autocorrect functionality. This option allows the device’s autocorrect to change the text as it sees fit.

密码ord选项将把文本显示为星号。

The Password option will display the text as asterisks.

键盘类型

Keyboard Types

选择自定义内容类型后,您可以选择键盘类型带有屏幕键盘的设备,此属性允许您选择在选择输入字段时显示哪个键盘。

When the Custom Content Type is selected, you have the option to select Keyboard Types. On devices with onscreen keyboards, this property allows you to select which keyboard will display when the Input Field is selected.

可能的选项是默认支持 ASCII数字和标点符号URL数字键盘电话键盘姓名电话键盘电子邮件地址社交搜索小数键盘一次性代码

The possible options are Default, ASCII Capable, Numbers And Punctuation, URL, Number Pad, Phone Pad, Name Phone Pad, Email Address, Social, Search, Decimal Pad, and One Time Code.

如果目标设备上没有所选的键盘,则将显示设备的默认键盘。

If the keyboard selected is not available on the target device, the device’s default keyboard will be displayed.

默认选项​显示设备的默认(字母)键盘。在大多数设备上,此键盘仅显示字母、空格键、退格键和回车键(Enter)。选择此选项后,玩家将能够切换到带有数字和标点符号键的键盘。

The Default option displays the device’s default (letters) keyboard. On most devices, this keyboard only displays letters, the Space key, Backspace key, and Return (Enter) key. When this option is selected, the player will have the ability to switch to the keyboard with numbers and punctuation keys.

例如iOS的英文默认键盘和数字及标点符号键盘之间可以轻松切换,如下图所示:

For example, the iOS English default keyboard and numbers and punctuation keyboard can easily be switched between, as shown in the following figure:

图13.15:iOS英文默认键盘以及数字和标点符号键盘

图13.15:iOS英文默认键盘以及数字和标点符号键盘

Figure 13.15: The iOS English default keyboard and numbers and punctuation keyboard

ASCII Capable选项显示带有标准 ASCII 键的设备键盘。此选项可用于将键盘限制为英语和类似语言的键盘。此键盘也是字母键盘,并且提供切换到数字和标点符号键盘的选项。例如,上图显示的是 iOS ASCII 键盘,因为它与默认英语键盘相同

The ASCII Capable option displays the device’s keyboard with standard ASCII keys. This option is available to restrict the keyboard to those of English and similar language keyboards. This keyboard is also a letters keyboard, and the option to switch to the numbers and punctuation keyboard is available. For example, the iOS ASCII keyboard is shown in the preceding diagram, as it is the same as the default English keyboard.

数字和标点符号选项打开设备的数字和标点符号键盘,并可以选择切换到“字母”键盘。例如,iOS 数字和标点符号键盘如上图所示

The Numbers And Punctuation option opens the device’s numbers and punctuation keyboard with the option to switch to the “letters” keyboard. For example, the iOS numbers and punctuation keyboard is shown in the preceding diagram.

URL 键盘选项会调出设备的 URL 键盘。此键盘有句点 ( . ) 键、正斜杠 ( / ) 键和.com键代替空格键。例如,下图显示了 iOS URL 键盘及其数字/标点符号形式。请注意,URL 键盘的数字/标点符号形式与默认/ASCII 键盘附带的数字和标点符号形式不同

The URL Keyboard option brings up the device’s URL keyboard. This keyboard has a period (.) key, forward-slash (/) key, and .com key in place of the Space Key. For example, the following image shows the iOS URL keyboard and its numbers/punctuation form. Note that the URL keyboard’s numbers/punctuation form is not the same as the numbers and punctuation form that accompanies the default/ASCII keyboard:

图 13.16:iOS URL 键盘及其数字/标点符号形式

图 13.16:iOS URL 键盘及其数字/标点符号形式

Figure 13.16: The iOS URL keyboard and its numbers/punctuation form

数字键盘选项显示设备的键盘,其中包含数字(0 - 9)和(通常)退格键。此键盘用于 PIN,因此不允许使用替代字符。例如,下图显示了 iOS 数字键盘键盘:

The Number Pad option displays the device’s keyboard with numbers (0-9) and (usually) a Backspace key. This keyboard is used for PINs, so it does not allow alternate characters. For example, the following image shows the iOS number pad keyboard:

图 13.17:数字键盘

图 13.17:数字键盘

Figure 13.17: The number pad keyboard

“电话键盘”选项显示设备的键盘,其按键与数字键盘相同,但还包含星号和井号(磅号)的按键。例如,下图显示了 iOS 电话键盘及其符号显示:

The Phone Pad option displays the device’s keyboard with the same keys as the number pad keyboard but also includes keys for the asterisk and hash sign (pound sign). For example, the following image shows the iOS phone pad keyboard and its symbol display:

图13.18:iOS手机键盘及其符号显示

图13.18:iOS手机键盘及其符号显示

Figure 13.18: The iOS phone pad keyboard and its symbol display

姓名电话键盘选项显示设备的“字母”键盘,并可以切换到手机键盘。例如,下图显示了 iOS 名称手机键盘的两种视图:

The Name Phone Pad option displays the device’s “letters” keyboard and can switch to the phone pad keyboard. For example, the following image shows the iOS name phone pad keyboard’s two views:

图 13.19:iOS 手机键盘的两种视图

图 13.19:iOS 手机键盘的两种视图

Figure 13.19: The iOS name phone pad keyboard’s two views

电子邮件地址选项显示设备的电子邮件键盘。电子邮件键盘突出显示@键和句点 ( . ) 键以及其他常见的电子邮件地址符号。例如,下图显示了 iOS 电子邮件键盘及其数字/标点符号形式:

The Email Address option shows the device’s email keyboard. The email keyboard prominently displays the @ key and the period (.) key as well as other common email address symbols. For example, the following image shows the iOS email keyboard and its numbers/punctuation form:

图 13.20:iOS 电子邮件键盘及其数字/标点符号形式

图 13.20:iOS 电子邮件键盘及其数字/标点符号形式

Figure 13.20: The iOS email keyboard and its numbers/punctuation form

社交键盘选项显示设备的社交键盘。此键盘突出显示常见的社交网络键,例如@键和#键。例如,下图显示了 iOS“Twitter”键盘及其数字/标点符号形式。在 iOS 设备上,此键盘被专门称为Twitter 键盘,但它会显示在 Instagram 等其他社交网络应用程序上

The Social Keyboard option displays the device’s social keyboard. This keyboard prominently displays common social networking keys such as the @ key and the # key. For example, the following image shows the iOS “Twitter” keyboard and its numbers/punctuation form. On the iOS device, this keyboard is specifically called the Twitter keyboard, but it displays on other social networking apps such as Instagram:

图 13.21:iOS Twitter 键盘

图 13.21:iOS Twitter 键盘

Figure 13.21: The iOS Twitter keyboard

“搜索”选项显示网络搜索键盘。此键盘突出显示空格键和句点键。例如,下图显示了 iOS 网络搜索键盘及其数字/标点符号形式:

The Search option displays the web search keyboard. This keyboard prominently displays the space and period keys. For example, the following image shows the iOS web search keyboard and its numbers/punctuation form:

图 13.22:iOS 网页搜索键盘及其数字/标点符号形式

图 13.22:iOS 网页搜索键盘及其数字/标点符号形式

Figure 13.22: The iOS web search keyboard and its numbers/punctuation form

笔记

Note

您可以在https://developer.apple.com/documentation/uikit/uikeyboardtype查看 iOS 上可用的所有键盘类型的列表

You can view a list of all the keyboard types available on iOS at https://developer.apple.com/documentation/uikit/uikeyboardtype.

您可以在https://developer.android.com/reference/android/w查看 Android 上可用的所有输入类型的列表(不仅仅是键盘,但它们也包含在列表中)idget/TextView.xhtml#attr_android:inputType

You can view a list of all input types (not just keyboard, but they are included in the list) available on Android at https://developer.android.com/reference/android/widget/TextView.xhtml#attr_android:inputType.

字符验证选项

Character Validation options

什么时候选择自定义内容类型后,您可以选择要使用的字符验证类型。此选项限制了可在输入字段中输入的字符类型。如果玩家尝试输入不符合限制的字符,则不会在输入字段中插入任​​何字符

When the Custom Content Type is selected, you have the option to select which type of Character Validation you would like to use. This option restricts the type of characters that can be entered in the Input Field. If the player attempts to enter a character that does not meet the restrictions, no character will be inserted in the Input Field.

可能的选项包括整数十进制URL字母数字名称电子邮件地址

The possible options are None, Integer, Decimal, URL, Alphanumeric, Name, and Email Address.

字符验证仅检查输入的每个字符,以查看该字符是否允许在字段中使用。它不会检查整个字符串以查看字符串本身是否有效。例如,如果选择了电子邮件地址,它不会检查它是否实际上是电子邮件地址的格式。这种类型的验证必须通过代码来完成。

Character Validation only checks each individual character being entered to see whether it is allowed within the field. It does not check the entire string to see whether the string itself is valid. For example, if Email Address is selected, it will not check whether it is actually in the format of an email address. That type of validation will have to be accomplished via code.

选项不执行任何字符验证,允许以任何格式将任何字符输入到输入字段中。

The None option does not perform any character validations, allowing any character to be entered into the Input Field with any formatting.

整数选项允许输入任何正整数或负整数值。这将限制输入仅允许数字09和破折号(负号)。输入进一步限制为仅允许将负号作为输入的第一个字符。

The Integer option allows any positive or negative integer value to be entered. This restricts the input to only allowing the digits 0 through 9 and the dash (negative symbol). The input is further restricted to allowing the negative symbol only as the first character entered.

十进制选项具有与整数选项相同的限制,但它还允许输入一个小数点。

The Decimal option has the same restriction as the Integer option, but it also allows a single decimal point to be entered.

字母数字选项仅允许使用英文字母(a 到 z)和数字 0 到 9。允许使用大写和小写字母;不接受负数符号和小数点

The Alphanumeric option only allows English letters (a through z) and the digits 0 through 9. Capital and lowercase letters are permitted; the negative symbol and decimal point are not accepted.

名称选项​允许名称中常见的字符并提供格式。它允许字母、空格和撇号 (')。它还强制将字符串中的第一个字符以及空格后的每个字符大写。空格不能跟在撇号后面,空格也不能跟在另一个空格后面。字符串中只允许一个撇号。字母不限于 az,就像字母数字选项一样。允许使用任何 Unicode 字母。

The Name option allows characters typically found in names and provides formatting. It allows letters, spaces, and an apostrophe (‘). It also enforces capitalization of the first character in the string and every character that comes after a space. A space cannot follow an apostrophe, and a space cannot follow another space. Only one apostrophe is allowed in the string. The letters are not restricted to just a-z as with the Alphanumeric option. Any Unicode letter is permitted.

笔记

Note

有关所有允许的 Unicode 字母的列表,请查看.NET 中Char.IsLetter方法的备注,网址为https://msdn.microsoft.com/en-us/library/system.char.isletter(v=vs.110).aspx

For a list of all allowable Unicode letters, check out the remarks on the Char.IsLetter method in .NET at https://msdn.microsoft.com/en-us/library/system.char.isletter(v=vs.110).aspx.

电子邮件地址选项允许在电子邮件地址中使用字符,并强制执行一些格式规则。与其他验证选项相比,它对可输入字符类型的限制明显较少。允许使用以下字符:

The Email Address option allows characters that are allowed within an email address and enforces a few formatting rules. It is significantly less restrictive in the types of characters that can be entered than the other validation options. The following characters are allowed:

  • 小写和大写英文字母(a至 z)
  • Lowercase and capital English letters (a through z)
  • 数字 0-9
  • Digits 0-9
  • 以下标点符号和特殊符号:
  • The following punctuation marks and special symbols:

符号名称

Symbol name

特点

Character

at 符号

at sign

@

@

点/句号

dot/period

.

问号

question mark

?

感叹号

exclamation point

!

连字符

hyphen

-

-

下划线

underscore

_

_

撇号

apostrophe

反引号

backtick

`

`

波浪号

tilde

~

~

打开和关闭括号

open and close braces

{和 }

{ and }

竖线

vertical bar

|

|

插入符号

caret

^

^

星号

asterisk

*

*

加号

plus sign

+

+

等号

equal sign

+

+

斜杠

forward slash

/

/

号/磅号

hash sign/pound sign

#

#

美元符号

dollar sign

$

$

百分比

percent

%

& 符号

ampersand

&

&

表 13.1:允许的特殊字符

Table 13.1: Permitted special characters

空间是不允许,字符串中只允许一个@符号,并且一个点不能跟在另一个点后面。

Spaces are not allowed, only one @ symbol is allowed in the string, and a dot cannot follow another dot.

尽管点作为电子邮件地址的第一个字符无效,但电子邮件地址字符验证选项不会限制它作为输入字段中输入的第一个字符。

Even though a dot as the first character of an email address is not valid, the Email Address Character Validation option does not restrict it from being the first character entered in the Input Field.

插入符号和选择的属性

Properties of the caret and selection

插入符号(也称为文本插入光标)是竖线用来表示键入时将插入文本。游戏进行时,第三个子项将自动添加到 InputField,名为InputField Input Caret。选择输入字段后,插入符号将变为可见。

A caret (also known as a text insertion cursor) is a vertical bar used to represent where text will be inserted when typed. When the game is playing, a third child is automatically added to InputField named InputField Input Caret. When the Input Field is selected, the caret becomes visible.

本节讨论的属性将影响插入符号的外观以及使用插入符号选择(或突出显示)文本时文本的外观。

The properties discussed in this section affect the look of the caret as well as the look of text if it is selected (or highlighted) using the caret.

插入符号闪烁率属性决定插入符号闪烁的速度。分配给此属性的数字表示插入符号每秒闪烁的次数。默认值为0.85

The Caret Blink Rate property determines how quickly the caret will blink. The number assigned to this property represents how many times the caret will blink per second. The default value is 0.85.

插入符号宽度属性决定插入符号的粗细(以像素为单位)。默认值为1

The Caret Width property determines how thick the caret is in pixels. The default value is 1.

当选择“自定义插入符号颜色”属性时,辅助属性“插入符号颜色”将变为可用。然后,您可以选择更改插入符号的颜色。除非选择了“自定义插入符号颜色”并更改了插入符号颜色,否则插入符号将为深​​灰色。

When the Custom Caret Color property is selected, a secondary property, Caret Color, becomes available. You then have the option to change the color of the caret. Unless Custom Caret Color is selected and the Caret Color is changed, the caret will be a dark grey color.

什么时候将插入符号拖到输入字段中的字符上,字符将被选中(或突出显示)。选择颜色属性决定所选文本的颜色xt.

When the caret is dragged across characters within the Input Field, the characters will be selected (or highlighted). The Selection Color property determines the color of the selected text.

输入字段默认事件 - 当值改变时(字符串)和当结束编辑时(字符串)

Input field default events – On Value Changed (String) and On End Edit (String)

输入字段组件有两个默认事件。第一个默认事件是“值改变时”事件,如输入字段组件的“值改变时(字符串)”部分所示。每当输入字段中的文本发生更改时,都会触发此事件。它接受字符串作为参数,其参数的使用方式与本章前面讨论的 UI 组件中的“值改变时”事件相同。如果要将参数传递给函数,可以从“静态参数”列表或“动态字符串”列表中选择该函数,具体取决于您希望如何或是否要将参数传递给函数:

The Input Field component has two default events. The first default event is the On Value Changed Event, as seen in the On Value Changed (String) section of the Input Field component. This event will trigger whenever the text within the Input Field is changed. It accepts a string as an argument, and its use of the argument works in the same way as the On Value Changed events from UI components discussed earlier in this chapter. If you want to pass a parameter to the function, you can select the function from either the Static Parameters list or from the Dynamic string list, depending on how or if you want an argument passed to the function:

图 13.23:第 13 章场景中输入字段上的值改变事件示例

图 13.23:第 13 章场景中输入字段上的值改变事件示例

Figure 13.23: On Value Changed Events on Input Field Example in the Chapter13 scene

如果您想不断检查玩家在输入字段中输入的内容,您可以使用上图所示的第三个设置,即从动态字符串列表中选择一个带有参数的函数

If you want to constantly check what the player is entering in the Input Field, you would use the third setup shown in the preceding image, which selects a function with a parameter from the Dynamic string list.

第二个默认事件是 On End Edit 事件,如输入字段组件的On End Edit (String)部分所示。每当玩家完成文本编辑时,都会触发此事件。玩家可以通过单击输入字段外部(这样输入就不再被选中)或提交文本来确认此完成。

The second default event is the On End Edit event, as seen in the On End Edit (String) section of the Input Field component. This event fires whenever the player completes editing the text. This completion is confirmed by the player either clicking outside of the Input Field (so that the Input is no longer selected) or by submitting the text.

接受字符串作为参数。与本章讨论的其他事件一样,您可以选择不传递参数、静态参数或动态参数。以下屏幕截图显示了所有三个选项的设置:

It accepts a string as an argument. As with the other events discussed in this chapter, you can choose to pass no argument, a static argument, or a dynamic argument. The following screenshot shows the setup for all three options:

图 13.24:第 13 章场景中输入字段上的结束编辑事件示例

图 13.24:第 13 章场景中输入字段上的结束编辑事件示例

Figure 13.24: On End Edit Events on Input Field Example in the Chapter13 Scene

如果希望在按下Enter键时调用 On End Edit 事件,请对行类型使用单行或多行提交选项

If you want to have the On End Edit event called when the Enter key is hit, use either the Single Line or Multi Line Submit options for the Line Type.

现在我们已经查看了 UI 输入字段,让我们查看它的对应项,输入字段 – TextMeshP反之。

Now that we’ve reviewed the UI Input Field, let’s review its counterpart, the Input Field – TextMeshPro.

输入字段 - TextMeshPro

Input Field - TextMeshPro

输入字段 - TextMeshPro 是与 UI 输入字段非常相似。添加到场景后,您会看到它看起来几乎完全相同,只是占位符文本的字体不同。UI 输入字段默认使用 Arial 字体,而输入字段 - TextMeshPro 使用 Liberation Sans。

The Input Field - TextMeshPro is very similar to the UI Input Field. When added to the scene, you’ll see it looks nearly identical, except that the placeholder text has a different font. The UI Input Field uses an Arial font by default, while the Input Field - TextMeshPro uses Liberation Sans.

要创建 UI 输入字段,请选择+ | UI |输入字段 - TextMeshPro。默认情况下,输入字段 - TextMeshPro 游戏对象有一个名为 Text Area 的子对象,它有两个子对象:一个占位符和一个文本对象。您会发现它的设置与 UI输入字段略有不同。

To create a UI Input Field, select + | UI | Input Field - TextMeshPro. By default, Input Field - TextMeshPro GameObject has a child named Text Area, which has two children: a Placeholder and a Text object. You will observe that it is slightly different in setup than the UI Input Field.

Text Area GameObject 包含一个 Rect Transform 组件和一个 Rect Mask 2D 组件。Text Area 确保文本不会出现在指定区域之外,如下图突出显示的区域所示。如果您想更改此区域的大小,可以更改 Rect Transform 组件上的属性

The Text Area GameObject contains a Rect Transform component and a Rect Mask 2D component. The Text Area ensures that the text does not appear outside of a specified area, as shown by the highlighted area in the following image. If you wanted to change the size of this area, you would change the properties on the Rect Transform component:

图 13.25:InputField 的文本区域 - TextMeshPro

图 13.25:InputField 的文本区域 - TextMeshPro

Figure 13.25: The Text Area of the InputField - TextMeshPro

Placeholder 和 Text 子项只是 Text - TextMeshPro 对象。您可以在第 10 章中找到有关 Text - TextMeshPro 对象的更多信息。

The Placeholder and Text children are simply Text - TextMeshPro objects. You can find more information about the Text - TextMeshPro objects in Chapter 10.

输入字段 - TextMeshPro GameObject 包含一个图像组件。如果要更改输入框的外观,请更改InputField ( TMP) pa上图像组件的源图像租。

An Input Field - TextMeshPro GameObject contains an Image component. If you want to change the appearance of the input box, change the Source Image of the Image component on the InputField (TMP) parent.

TextMeshPro - 输入字段组件

TextMeshPro - Input Field component

父 InputField (TMP) 对象有一个TextMeshPro – 输入字段组件。它具有可交互 UI 对象共有的所有属性、标准 UI 输入字段的许多相同属性以及一些输入字段 - TextMeshPros 独有的属性。本节将不讨论输入字段 - TextMeshPros 与 UI 输入字段共享的属性,因为它们已在上一节中讨论过,我们将仅讨论其独有的属性。您可以在下图中看到这些属性:

The parent InputField (TMP) object has a TextMeshPro – Input Field component. It has all the properties common to the interactable UI objects, many of the same properties of the standard UI Input Field, and a few that are exclusive to Input Field - TextMeshPros. This section will not discuss the properties that Input Field - TextMeshPros share with UI Input Fields since they were discussed in the previous section, and we will only discuss those that are exclusive to it. You can see the properties in the following image:

图 13.26:TextMeshPro – 输入字段组件

图 13.26:TextMeshPro – 输入字段组件

Figure 13.26: The TextMeshPro – Input Field component

Text Viewport属性设置为输入文本应在哪个区域的 Rect Transform 中显示。可见。文本区域的 Rect Transform默认情况下,此属性分配给了 child。如前所述,Text Area child 有一个 Rect Mask 2D 组件,可防止文本在Text Area 的 Rect Transform 组件定义的区域之外可见。

The Text Viewport property is set to the Rect Transform of the area in which the entered text should be visible. The Rect Transform of Text Area child is assigned to this property, by default. As stated earlier, the Text Area child has a Rect Mask 2D component that stops text from becoming visible outside of the area defined by the Rect Transform component of the Text Area.

Text Component属性设置为应显示输入文本的对象的 Text Mesh Pro UGUI 组件。分配给此属性的 TextMeshPro - Text 对象将确定输入文本的字体和显示设置。默认情况下,Text 子项的 Text Mesh Pro UGUI 组件被分配给此属性。

The Text Component property is set to the Text Mesh Pro UGUI component of the object in which the entered text should display. The TextMeshPro - Text object assigned to this property will determine the font and display settings of the entered text. The Text Mesh Pro UGUI component of the Text child is assigned to this property, by default.

文本输入框组可以展开以显示较大的文本输入区域。文本输入框属性的工作方式与UI 输入的输入字段组件上的文本属性相同字段对象。用户输入的文本将被存储此处,可通过代码访问。这将存储输入的实际文本,而不是格式化的文本。例如,如果文本已格式化为显示为星号(如Pin密码内容类型),则实际的 PIN 或密码将存储在此处,而不是字符串星号。

The Text Input Box group can be expanded to display a large text input area. The Text Input Box property works the same way as the Text property on the Input Field component of UI Input Field objects. The text entered by the user will be stored here and can be accessed by code. This will store the actual text entered and not the formatted text. For example, if the text has been formatted to appear as asterisks (as with Pin and Password Content Types), the actual pin or password will be stored here rather than a string of asterisks.

输入字段设置

Input Field settings

字体资源属性确定输入字段 - TextMeshPro 中显示的各种文本的字体,Point Size属性确定文本的大小。您会注意到 Placeholder 和 Text 子项在其 Text Mesh Pro UGUI 组件上也具有Font AssetPoint Size属性。更改输入字段 - TextMeshPro 父项上的Font AssetPoint Size属性也会更改子对象上的相应属性。

The Font Asset property determines the font of the various texts displayed within the Input Field - TextMeshPro, and the Point Size property determines the size of the text. You’ll note that the Placeholder and Text children also have the Font Asset and Point Size properties on their Text Mesh Pro UGUI components. Changing the Font Asset and Point Size properties on the Input Field - TextMeshPro parent will also change the corresponding properties on the child objects.

此组中的其余属性均包含在 UI放字段。

The rest of the properties in this group are the ones included within the UI Input Field.

控制设置

Control settings

如果选择了OnFocus - Select All属性后,当选择了输入字段 - TextMeshPro 时,字段内的所有文本都会被高亮显示。

If the OnFocus - Select All property is selected, when the Input Field - TextMeshPro is selected, all the text within the field will be highlighted.

如果选择了“Reset On DeActivation”属性,则插入符号将重置为文本前面的默认位置。

If the Reset On DeActivation property is selected, the caret will reset to the default position at the front of the text.

如果选择了“按 ESC 键恢复”属性,则按下ESC键时文本将重置为默认值。默认值为空字符串或场景启动时在文本输入框中输入的任何内容

If the Restore on ESC Key property is selected, the text will reset back to the default when the esc key is hit. The default will be either an empty string or whatever is entered in the Text Input Box when the scene starts.

富文本属性表示接受任何富文本标签,允许富文本编辑属性允许用户在 该领域。

The Rich Text property means that any rich text tags to be accepted, and the Allow Rich Text Editing property allows the user to enter rich text tags within the field.

输入字段 - TextMeshPro 默认事件 - 选择时(字符串)和取消选择时(字符串)

Input Field - TextMeshPro default events – On Select (String) and On Deselect (String)

输入字段 - TextMeshPro 有四个默认事件:值改变时事件、结束编辑时事件、选择时事件和取消选择时事件,如值改变时(字符串)结束编辑时(字符串)选择时(字符串)取消选择时(字符串)部分所示。

The Input Field - TextMeshPro has four default events: the On Value Changed Event, the On End Edit Event, the On Select Event, and the On Deselect Event, as shown in the On Value Changed (String), On End Edit (String), On Select (String), and On Deselect (String) sections.

前两个事件,即“值改变时事件”和“结束编辑时事件”与 UI 输入字段中显示的事件相同

The first two events, the On Value Changed Event and the On End Edit Event are the same as those presented in the UI Input Field.

第三个事件是On Select事件。每当选择输入字段 - TextMeshPro 时,都会触发此事件。第四个事件是On Deselect事件。正如您所料,每当取消选择输入字段 - TextMeshPro 时,都会触发此事件。它的工作原理类似于On End Edit事件,只是它在提交文本时不会触发。

The third event is the On Select Event. This event fires whenever the Input Field - TextMeshPro is selected. The fourth event is the On Deselect Event. As you would expect, the event fires whenever the Input Field - TextMeshPro is deselected. It works similarly to the On End Edit event, except that it does not fire when the text is submitted.

与本章讨论的其他事件一样,您可以选择不向On Select 事件On Deselect事件传递参数、传递静态参数或传递动态参数。

As with the other events discussed in this chapter, you can choose to pass no argument, a static argument, or a dynamic argument to the On Select and On Deselect events.

现在我们已经回顾了 uGUI 的各种可交互组件,让我们看一些示例来了解使用它们。

Now that we’ve reviewed the various interactable components of the uGUI, let’s look at some examples of how to use them.

示例

Examples

本章包含如此多的新内容,以至于我可以用本书的其余部分来向您展示示例!遗憾的是,我不能这样做,所以我将向您展示一些我希望最有用的示例。L让我们开始吧。

This chapter has so many new items in it that I could spend the rest of this book just showing you examples! Sadly, I can’t do that, so I will show you examples that I hope will be the most useful. Let’s begin.

创建带有图像的下拉菜单

Creating a dropdown menu with images

让我们继续处理我们的场景并创建一个下拉菜单,让我们可以在猫和狗之间切换玩家角色。最终版本将如下所示:

Let’s continue working on our scene and create a dropdown menu that will allow us to swap our player character between a cat and a dog. The final version will appear as follows:

图 13.27:暂停菜单下拉菜单的最终版本

图 13.27:暂停菜单下拉菜单的最终版本

Figure 13.27: The final version of the Paused Menu dropdown

改变我们的选择将会改变屏幕顶部出现的角色图像。

Changing our selection will then change the image of the character that appears at the top of the screen.

包含狗图像的精灵表是我从此处的免费艺术资产中修改而来的资产

The spritesheet containing the dog image is an asset that I’ve modified from free art assets found here:

https://opengameart.org/content/cat-dog-free-sprites

https://opengameart.org/content/cat-dog-free-sprites

这与为我们提供猫精灵的资产相同

This is the same asset that provided us with the cat sprites.

当你想在播放模式下查看 UI 下拉菜单时,你必须按P键调出暂停面板。当你只想快速检查布局时,这可能会有点烦人。你可以暂时禁用主摄像头上的ShowHidePanels.cs脚本来禁用暂停面板的自动隐藏。只需记住在完成了!

When you want to see your UI Dropdown menu in play mode, you have to press P to bring up the Pause Panel. This can be kind of annoying when you just want to quickly check the layout. You can disable the automatic hiding of the Pause Panel momentarily by disabling the ShowHidePanels.cs script on the Main Camera. Just remember to turn it back on when you are done!

使用标题和项目图像布局下拉菜单

Laying out the dropdown with caption and item images

要创建一个UI下拉菜单像上图所示,完成以下步骤:

To create a UI Dropdown menu like the one shown in the previous image, complete the following steps:

  1. 找到文本源文件中提供的dogSprites.png图像并将其带入项目Assets/Sprites文件夹。
  2. Locate the dogSprites.png image provided in the source files of the text and bring it in to the Assets/Sprites folder of your project.
  3. 将Sprite 模式设置为“多个”,然后使用“自动切片”功能对Spritesheet 进行切片。执行“自动切片”时,将“枢轴”设置 “底部”
  4. Slice the spritesheet by setting its Sprite Mode to Multiple and utilizing Automatic slicing. When you perform the Automatic slice, set the Pivot to Bottom.
  5. 现在,让我们在暂停面板中添加一个 UI 下拉菜单。右键单击层次结构中的暂停面板,然后选择UI |下拉菜单。将下拉菜单在层次结构中向上移动,以便它列在暂停横幅的正下方。
  6. Now, let’s add a UI Dropdown to our Pause Panel. Right-click on the Pause Panel in the Hierarchy and select UI | Dropdown. Move the Dropdown upward in the Hierarchy so that it is listed right below Pause Banner.
  7. 通过设置Dropdown的 Rect Transform 属性来调整其大小和位置,如下所示:
    图 13.28:下拉菜单的矩形变换

    图 13.28:下拉菜单的矩形变换

    你的下拉菜单现在应该如下所示:

    图 13.29:下拉菜单的当前状态

    图 13.29:下拉菜单的当前状态

  8. Adjust the size and position of the Dropdown by setting its Rect Transform properties, as follows:

    Figure 13.28: The Rect Transform of the Dropdown

    Your Dropdown should now appear as follows:

    Figure 13.29: The current state of the Dropdown

  1. 更改 Dropdown 背景,我们需要更改Dropdown 的 Image 组件上的Source Image 。将uiElements_12子精灵分配到Source Image中。

    展开层次结构中的下拉菜单以查看其子项,将显示一个名为Arrow的子项。您可以调整Arrow的属性来更改其外观和一般位置。

    为Arrow提供uiElements_132精灵并调整 Rect Transform,如图所示:

  2. To change the Dropdown background, we need to change the Source Image on the Image component of the Dropdown. Assign the uiElements_12 subsprite into the Source Image.

    Expanding the Dropdown in the Hierarchy to view its children will reveal a child named Arrow. You can adjust the properties of Arrow to change its look and general position.

    Give the Arrow the uiElements_132 sprite and adjust the Rect Transform, as illustrated:

图 13.30:箭头的矩形变换

图 13.30:箭头的矩形变换

Figure 13.30: The Rect Transform of the Arrow

笔记

Note

在项目视图的搜索栏中输入132可以快速找到uiElements_132图像。

Typing 132 in the search bar of the Project view is a quick way to find the uiElements_132 image.

  1. 虽然Dropdown 组件有一个用于标题的变量,但 UI Dropdown 模板并未预先构建该变量。因此,我们必须手动添加一个。在层次结构中右键单击Dropdown,然后选择UI | Image为其添加子 Image。将其在层次结构中的位置移动到Label上方。将其重命名为Caption
  2. While the Dropdown component has a variable for a caption, the UI Dropdown template does not come prebuilt with one. So, we have to manually add one in ourselves. Right-click on Dropdown in the Hierarchy and select UI | Image to give it a child Image. Move its position in the Hierarchy so that it is above Label. Rename it Caption.
  3. 为Caption提供catSprites_0精灵并调整其 Rect Transform,如下所示:
    图 13.31:标题的矩形变换

    图 13.31:标题的矩形变换

    我们已经设定将它更改为猫的图像,以便我们可以看到它是否正确显示,但请记住它会根据选择自动更改为适当的精灵。

  4. Give Caption the catSprites_0 sprite and adjust its Rect Transform, as follows:

    Figure 13.31: The Rect Transform of the Caption

    We’ve set it to the image of the cat so that we can see whether it is displaying properly but remember that it will automatically change to the appropriate sprite based on the selection.

  1. 现在,选择Label并调整其Rect TransformText组件,如下所示:
    图 13.32:标签的矩形变换

    图 13.32:标签的矩形变换

    如果您尝试调整文本,它将恢复为选项 A,因为这是由下拉列表组件驱动的。

  2. Now, select Label and adjust its Rect Transform and Text components as shown:

    Figure 13.32: The Rect Transform of the Label

    If you try to adjust the Text, it will revert to Option A, since this is driven by the Dropdown component.

  1. 现在我们已经按我们想要的方式设置了标题,让我们开始处理模板。启用模板对象,以便您可以在场景中看到它,并在层次结构中展开它以查看其所有子项。

    我们不会使用Scrollbar,因此我们可以保留其原样。它将显示在 Scene 视图中,但在 Play 模式下不可见,因为其Visibility在Template 的Scroll Rect 组件中设置为Auto Hide And Expand Viewport 。

  2. Now that we have our caption set up the way we want it, let’s work on the Template. Enable the Template object so that you can see it in the scene and expand it in the Hierarchy to view all of its children.

    We won’t use the Scrollbar, so we can leave it as it is. It will show up in the Scene view but will not be visible in Play mode, because its Visibility is set to Auto Hide And Expand Viewport in the Scroll Rect component of Template.

  3. 要更改下拉窗口的背景,请将模板的图像组件上的源图像更改uiElements_11
  4. To change the background of the window that drops down, change the Source Image on Image component of Template to uiElements_11.
  5. Item子项显示下拉菜单中列出的所有选项的一般格式。我们对它所做的任何更改都将在下拉菜单脚本填充所有选项时自动应用于它们。

    选择Item并将其 Rect Transform Height更改 50

  6. The Item child shows the general format to all options that will be listed in our Dropdown. Any changes we make to it will be automatically applied to all options when the Dropdown script populates them.

    Select Item and change its Rect Transform Height to 50.

图 13.33:项目的矩形变换和层次结构

图 13.33:项目的矩形变换和层次结构

Figure 13.33: The Rect Transform and Hierarchy of the Item

  1. 内容需求完全封装Item 因此,将其 Rect Transform Height更改为52。确保Pos Y0

    如果你玩过这个游戏,Pos Y会在Content的 Rect Transform 上从0变为其他值。这实际上是应该发生的(选项 A设置在标题后面),但当你试图布置你的Item时,这会很烦人,因为你将无法看到你的Item

    因此,播放完毕后,将Pos Y改回0,即可继续编辑。

  2. Content needs to fully encapsulate the Item. So, change its Rect Transform Height to 52. Ensure that Pos Y is at 0.

    If you played the game, the Pos Y will change on Rect Transform of Content from 0 to something else. This is actually supposed to happen (option A is being set behind the caption), but it’s annoying when you are trying to lay out your Item, because you won’t be able to see your Item.

    Therefore, after playing, change Pos Y back to 0 so that you can continue editing.

  3. 正如我们必须向Dropdown添加子图像以便可以获得标题图像一样,我们还必须向Item添加子图像,这样我们才能在菜单中显示图像。

    Hierarchy中右键单击Item并选择UI | Image为其添加子Image。将其重命名为Item Image并将其移动到HierarchyItem CheckmarkItem Label之间

  4. Just as we had to add a child Image to Dropdown so we could have a caption image, we also have to add a child Image to Item, so we can have an image display in the menu.

    Right-click on Item in the Hierarchy and select UI | Image to give it a child Image. Rename it Item Image and move it between Item Checkmark and Item Label in the Hierarchy.

  5. 为Item Image赋予catSprites_0精灵并调整其 Rect Transform,如下所示:
  6. Give Item Image the catSprites_0 sprite and adjust its Rect Transform, as shown:
图 13.34:项目图像的矩形变换和层次结构

图 13.34:项目图像的矩形变换和层次结构

Figure 13.34: The Rect Transform and Hierarchy of the Item Image

  1. 选择商品标签调整其Rect TransformText组件,如下所示:
  2. Select Item Label and adjust its Rect Transform and Text components, as follows:
图 13.35:项目标签的矩形变换

图 13.35:项目标签的矩形变换

Figure 13.35: The Rect Transform of the Item Label

  1. 最后对Item要做的一件事是删除白色背景。选择Item Background并将Image 组件的Color属性上的alpha更改为0。您的下拉菜单现在应如下所示:
  2. The last thing to do to Item is to remove the white background. Select Item Background and change the alpha on the Color property of the Image component to 0. Your Dropdown menu should now look as follows:
图 13.36:下拉菜单的当前状态

图 13.36:下拉菜单的当前状态

Figure 13.36: The current state of the dropdown

  1. 现在我们我们已经直观地设置了 Dropdown,我们需要设置 Dropdown 组件的属性。如果你玩游戏,你会看到我们的 Dropdown 还没有正确的选项。你无法从游戏中看出来,但Caption ImageItem Image也没有连接:
  2. Now that we have set up our Dropdown visually, we need to set up the properties of the Dropdown component. If you play the game, you will see that our Dropdown doesn’t have the correct options yet. You can’t tell from playing it, but Caption Image and Item Image also aren’t hooked up:
图 13.37:按下“播放”按钮时下拉菜单的当前状态

图 13.37:按下“播放”按钮时下拉菜单的当前状态

Figure 13.37: The current state of the dropdown when pressing Play

  1. 让我们更新Dropdown上的 Dropdown 组件。将Caption子项从 Hierarchy拖到Caption Image属性中,将Item Image拖到Item Image属性中。当您将Caption拖到Caption Image中时,猫的图像将从场景中消失。别担心!它会回来的。它正在更新为Option A的图像,该图像现在设置为无。
  2. Let’s update the Dropdown component on Dropdown. Drag the Caption child from the Hierarchy into the Caption Image property and Item Image into the Item Image property. When you drag Caption into Caption Image, the image of the cat will disappear from the scene. Don’t worry! It will come back. It’s updating to the image of Option A, which is set to nothing right now.
  3. 最后我们需要设置的是在下拉菜单中显示的选项集。

    我们只需要两个选项,因此选择选项 C,然后点击减号 ( - ) 按钮将其删除。现在,将文本选项 A更改为Cat,将文本选项 B 更改为Dog

    catSprites_0拖到Cat下的精灵槽中,将dogSprites_0拖到Dog下的精灵槽中。您的 Dropdown 组件属性应显示如下:

    图 13.38:Dropdown 组件的属性

    图 13.38:Dropdown 组件的属性

    如果你玩游戏,你会看到下拉菜单现在显示了适当的选项列表和标题根据您的选择更新图像和文本:

    图 13.39:下拉选项的最终视觉设置

    图 13.39:水滴的最终视觉效果向下选项

  4. The last thing we need to set up is the set of options that will display in the menu that drops down.

    We only need two options, so select Option C and then hit the minus (-) button to delete it. Now, change the text Option A to Cat and the text Option B to Dog.

    Drag catSprites_0 into the sprite slot under Cat and dogSprites_0 into the sprite slot under Dog. Your Dropdown component properties should appear, as follows:

    Figure 13.38: The properties of the Dropdown component

    If you play the game, you will see that the dropdown now shows the appropriate list of options and the caption image and text update based on your selection:

    Figure 13.39: The final visual set up of the dropdown options

使用下拉选择中的信息

Using the information from the dropdown selection

现在我们的下拉菜单看起来符合我们的要求,并且功能正常,我们可以通过代码访问玩家的选择。我们将使用玩家的选择来更新屏幕左上角的玩家角色图像。

Now that our Dropdown looks the way we want it and is functioning properly, we can access the player’s selection with code. We’ll use the player’s selection to update the player character image in the top-left corner of the screen.

要将玩家角色图像与下拉菜单中的选择交换,请完成以下步骤:

To swap the player character image with the selection from the Dropdown, complete the following steps:

  1. 在Assets/Scripts文件夹中创建一个名为PlayerCharacterSwap.cs的新 C# 脚本。
  2. Create a new C# script in your Assets/Scripts folder named PlayerCharacterSwap.cs.
  3. 为了访问 UI 变量类型,请使用以下行将UnityEngine.UI命名空间添加到脚本顶部
    使用 UnityEngine.UI;
  4. For us to access UI variable types, add the UnityEngine.UI namespace to the top of the script with the following line:
    using UnityEngine.UI;
  5. 我们只需要两个变量,一个表示将与下拉菜单中的选择交换的图像,另一个表示下拉菜单。

    我们将此脚本附加到 Dropdown 对象,这样我们就不必将引用它的变量公开。在命名空间声明后添加以下变量声明:

    公共图像角色图像;
    下拉列表 dropDown;
  6. We only need two variables, one to represent the Image that will be swapped with the selection from the Dropdown menu and one to represent the Dropdown menu.

    We’ll attach this script to the Dropdown object, so we don’t have to make the variable referencing it public. Add the following variable declarations after the namespace declarations:

    public Image characterImage;
    Dropdown dropDown;
  7. 使用附加到此脚本将附加到的对象上的 Dropdown 组件初始化Awake()方法中的dropDown变量
    无效唤醒(){
        dropDown = GetComponent<下拉列表>();
    }
  8. Initialize the dropDown variable in an Awake() method with the Dropdown component attached to the object this script will be attached to:
    void Awake(){
        dropDown = GetComponent<Dropdown>();
    }
  9. 默认Dropdown 组件上的事件是On Value Changed事件,它接受一个整数参数。创建一个接受整数参数的公共函数,如下所示:
    公共无效DropDownSelection(int选择索引){
    }
  10. The default event on the Dropdown component is the On Value Changed event, and it accepts an integer argument. Create a public function that accepts an integer parameter with the following:
    public void DropDownSelection(int selectionIndex){
    }
  11. DropDownSelection方法将从Dropdown 组件中获取 Value 属性的整数值。Value表示当前选项列表中选择的选项的索引。如果我们将Value的值作为参数传递给函数,那么我们就可以在脚本中使用choiceIndex参数引用它。

    将以下两行添加到DropDownSelection函数中:

    Debug.Log("玩家选择了" + dropDown.options[selectionIndex].text);
    字符图像.sprite = dropDown.options[selectionIndex].图像;

    第一行将在选项列表中的指定索引处找到选项上的文本并将其打印到控制台。

    第二行将在选项列表中的指定索引处找到选项上的精灵,并将角色图像上的精灵更改为该精灵。

  12. The DropDownSelection method will get the integer value of the Value property from the Dropdown component. Value represents the index of the option currently selected within the Options list. If we pass the value of Value as an argument to the function, we can then reference it with the selectionIndex parameter within our script.

    Add the following two lines to your DropDownSelection function:

    Debug.Log("player selected " + dropDown.options[selectionIndex].text);
    characterImage.sprite = dropDown.options[selectionIndex].image;

    The first line will find the text on the option at the specified index in the options list and print it to the console.

    The second line will find the sprite on the option at the specified index in the options list and change the sprite on the characterImage to that sprite.

  13. 现在脚本已经完成,可以将其连接到 Unity 编辑器中。将PlayerCharacterSwap.cs脚本拖到Dropdown上进行连接。

    请记住,dropDown变量不是公共的,因为我们希望将此脚本作为组件附加Dropdown

  14. We’re now done with the script and can hook it up in the Unity Editor. Drag the PlayerCharacterSwap.cs script onto Dropdown to attach it.

    Remember, the dropDown variable is not public, because we expected to attach this script as a component to the Dropdown.

  15. 需要在 Inspector 中分​​配公共变量characterImage 。将Character ImageHierarchyHUD Canvas | Top Left Panel | Character Holder | Character)拖到Player Character Swap组件上的Character Image插槽中
  16. The public variable characterImage needs to be assigned in the Inspector. Drag the Character Image from the Hierarchy (HUD Canvas | Top Left Panel | Character Holder | Character) to the Character Image slot on the Player Character Swap component.
  17. 现在我们需要从 Dropdown 组件上的On Value Changed事件中调用PlayerCharacterSwap.cs脚本上的DropDownSelection函数。选择On Value Changed (Int32)事件列表中的加号 ( + )以添加新的On Value Changed事件。将DropdownHeirarchy拖到对象槽中,然后从PlayerCharacterSwap脚本的Dynamic int列表中选择DropDownSelection函数。On Value Changed (Int32)事件列表应如下所示:
  18. Now we need to call the DropDownSelection function on the PlayerCharacterSwap.cs script from the On Value Changed event on the Dropdown component. Select the plus sign (+) in the On Value Changed (Int32) event list to add a new On Value Changed event. Drag Dropdown from the Heirarchy into the object slot and select the DropDownSelection function from the Dynamic int list of the PlayerCharacterSwap script. The On Value Changed (Int32) event list should appear as follows:
图 13.40:On Value Changed(Int32)属性

图 13.40:On Value Changed(Int32)属性

Figure 13.40: The On Value Changed (Int32) property

就是这样!现在玩游戏,观察玩家角色的图像与下拉菜单中选择的图像交换:

That’s it! Now play the game and watch the player character’s image swap with the image selected from the Dropdown:

图 13.41:场景的最终版本

图 13.41:场景的最终版本

Figure 13.41: The final version of the scene

13.41显示场景的最终版本游戏的。

Figure 13.41 shows the final version of the scene of the game.

概括

Summary

谁知道有这么多不同类型的可交互 UI 对象?拥有这些不同 UI 对象的模板非常有用。从技术上讲,它们都可以用按钮、图像和文本手动构建,但这需要很多努力,您不必担心,因为 Unity 已经为您完成了。在本章中,我们回顾了如何使用常见的 UI 元素:切换、滑块、下拉菜单和输入字段。

Who knew there were so many different types of interactable UI objects? Having templates for these different UI objects is incredibly helpful. Technically, they can all be built by hand with Buttons, Images, and Text, but that would take a lot of effort you don’t have to worry about because Unity has done it for you. In this chapter, we reviewed how to use the common UI elements: Toggle, Slider, Dropdown, and Input Field.

接下来,我们将介绍如何在UI 中使用动画!

Next, we will cover using animations within the UI!

第 4 部分:Unity UI 高级主题

Part 4: Unity UI Advanced Topics

在本部分中,您将了解与 Unity UI 系统相关的更多高级主题。您将学习如何为 UI 元素设置动画以及在 UI 中显示粒子。您将学习如何创建出现在游戏世界中的 UI。最后,您将获得注意事项概述,以确保创建优化的 UI。

In this part, you’ll learn about more advanced topics related to the Unity UI system. You’ll learn how to animate UI elements as well as display particles within the UI. You’ll learn how to create UI that appears within the world of your game. Lastly, you’ll get an overview of considerations to make sure you create an optimized UI.

本部分包含以下章节:

This part has the following chapters:

14

14

动画 UI 元素

Animating UI Elements

由于我们已经讨论了如何为按钮创建动画过渡,在本章中,我们将更彻底地了解动画过渡,并讨论如何以更一般的意义为 UI 元素创建动画。

Since we have already discussed how to create animation transitions for buttons, in this chapter, we’ll take a look at animation transitions more thoroughly and discuss how to create animations for UI elements in a more general sense.

本章假设您对 Unity 的动画系统有基本的了解,因此不会详细描述各种菜单的名称以及动画窗口和动画器窗口的布局。动画剪辑和动画器将简要描述,重点介绍它们与 UI 的关系及其实现,这将在本章末尾的示例中讨论。

This chapter assumes that you have a basic understanding of Unity’s Animation System and will not go into detail describing the names of the various menus and layout of the Animation Window and Animator Window. Animation Clips and Animators will be described briefly, with a focus on how they relate to UI and their implementation, which will be discussed in the examples at the end of the chapter.

在本章中,我们将讨论以下主题:

In this chapter, we will discuss the following topics:

  • 将动画应用于各种UI 元素
  • Applying animations to the various UI elements
  • 创建淡入淡出的弹出窗口
  • Creating a pop-up window that fades in and out
  • 使用状态机和动画事件创建复杂的动画系统
  • Creating a complex Animation System with a State Machine and Animation Events

尽管我假设您对 Unity 的动画系统有基本的了解,但我还是想强调动画剪辑和动画器之间的区别。

Even though I am assuming that you have a basic understanding of Unity’s Animation System, I do want to emphasize the difference between an Animation Clip and the Animator.

在 Unity 中为项目创建动画时,首先要使用动画剪辑。动画剪辑应代表单个不同的动作或运动。例如,如果您有一个菜单执行两个单独的操作,即弹跳和缩放,则应将每个操作都设置为单独的动画剪辑。虽然您可以在一个动画剪辑中发生多件事,但除非它们总是同时发生,否则不要将多个动作放在一个剪辑中,这一点非常重要

When creating animations for items in Unity, you start with Animation Clips. An Animation Clip should represent a single distinct action or motion. So, for example, if you had a menu that performed two separate actions, bouncing and zooming, you’d make each of those actions a separate Animation Clip. Although you can have multiple things happen in a single Animation Clip, it is very important not to put multiple actions in a single clip unless they are always going to happen at the same time.

每个游戏对象都可以有多个动画剪辑。动画器决定了所有这些动画如何链接在一起。因此,游戏对象的动画器将包含其所有动画剪辑

Every GameObject can have multiple Animation Clips. The Animator determines how all of these Animations link together. So, a GameObject’s Animator will have all of its Animation Clips within it.

我想做出这种区分,因为在过去,我见过一些项目,其中的史诗动画剪辑包含多个动作,而这些动作应该分解为更简单的动作。

I wanted to make this distinction because in the past, I have seen projects with epic Animation Clips containing multiple actions that should have been broken down into more simple motions.

笔记

Note

与前面的章节一样,本节中展示的所有示例都可以在代码包中提供的 Unity 项目中找到。它们可以在标记为Chapter14 的场景中找到

As with previous chapters, all of the examples shown in this section can be found within the Unity project provided in the code bundle. They can be found within the scene labeled Chapter14.

技术要求

Technical requirements

您可以在此处找到本章的相关代码和资产文件https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2014

You can find the relevant codes and asset files of this chapter here: https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2014

动画剪辑

Animation Clips

Unity 的伟大之处动画系统的优点是您可以为 UI 的几乎任何属性设置动画。要创建动画剪辑,只需打开动画窗口(窗口|动画Ctrl + 6),然后选择要设置动画的 UI 元素,然后选择创建

The great thing about the Unity Animation System is that you can animate nearly any property of the UI. To create an Animation Clip, simply open the Animation Window (Window | Animation or Ctrl + 6), and with the UI element you want to animate selected, select Create:

图 14.1:动画窗口

图 14.1:动画窗口

Figure 14.1: The Animation Window

完成后,系统将提示您保存动画剪辑。

Once you do so, you’ll be prompted to save the Animation Clip.

创建动画剪辑后,您可以通过单击“添加属性”将任何属性添加到剪辑的时间轴

After creating the Animation Clip, you can then add any property to the clip’s timeline by clicking on Add Property:

图 14.2:SliderExampleAnimation 时间轴

图 14.2:SliderExampleAnimation 时间轴

Figure 14.2: The SliderExampleAnimation timeline

这样做会调出每一个对象的组件,以及其所有子项的列表:

Doing so will bring up every component of the object, as well as a list of all of its children:

图 14.3:添加动画属性

图 14.3:添加动画属性

Figure 14.3: Adding an Animation property

您还可以查看每个子项的组件和子项

You can also view the components and children of each child:

图 14.4:扩展动画组件

图 14.4:扩展动画组件

Figure 14.4: Expanding Animation components

然后,您可以查看这些子组件及其子组件。您可以继续此方式,直到您用尽了嵌套在所选 GameObject 下的 GameObject 列表

Then, you can view the components and children of those children. You can continue in this manner until you have exhausted the list of GameObjects that are nested under the selected GameObject.

如果你展开 GameObject 的组件或其子组件之一,你会看到该组件的所有可动画属性的列表。如下面的屏幕截图所示,Slider组件上的几乎每个属性都可以通过这种方式进行动画处理:

If you expand a component of a GameObject or one of its children, you will then note a list of all properties of that component that can be animated. As you will notice in the following screenshot, almost every property on the Slider component can be animated in this way:

图 14.5:Slider 组件及其动画属性

图 14.5:Slider 组件及其动画属性

Figure 14.5: The Slider component and its Animatable properties

只有具有以下数据类型的属性才可以使用动画系统进行动画处理:

Only properties that have the following data types can be animated with the Animation System:

  • Vector2Vector3Vector4
  • Vector2, Vector3, and Vector4
  • 四元数
  • Quaternion
  • 布尔值
  • bool
  • 漂浮
  • float
  • 颜色
  • Color

选择加号会将属性连同两个关键帧一起添加到动画时间轴。然后您可以更改各个关键帧处每个属性的值

Selecting the plus sign will add the property to the Animation timeline along with two keyframes. You can then change the values of each property at the various keyframes.

关键帧是动画中的重要或关键帧(因此得名)。它表示动画中过渡的起点或终点。

A keyframe is an important or key (hence the name) frame within an animation. It represents the start or end point of a transition within an animation.

图 14.6:SliderExampleAnimation 第一个关键帧

图 14.6:SliderExampleAnimation 第一个关键帧

Figure 14.6: SliderExampleAnimation first keyframe

在上面的截图中,红色方框内的值表示该属性在特定帧的值。布尔值用复选框表示,浮点值用数字表示。每种类型都可以通过选择直接编辑。

In the preceding screenshot, the values encased in the red boxes represent the value of the property at the particular frame. Boolean values are represented by checkboxes, and float values are represented by numbers. Each type can be edited directly by selecting them.

Unity 将在关键帧之间填充值,以便它们在曲线上变化(或插值)。您可以通过选择“曲线”选项卡来查看插值曲线。

Unity will fill in the values between the keyframes so that they change (or interpolate) on a curve. You can view the interpolation curve by selecting the Curves tab.

您可以通过播放动画或拖动播放头来观察所有帧中发生的变化。

You can watch the changes occur throughout all frames by playing the animation or scrubbing the playhead.

播放头是指示当前正在显示哪一帧的标记。“拖动播放头”是指在时间线上拖动播放头以查看各个帧的变化。

The playhead is the marker that indicates what frame is currently being displayed. “Scrubbing the playhead” means dragging the playhead across the timeline to view changes over individual frames.

图 14.7:曲线版本的时间线

图 14.7:曲线版本的时间线

Figure 14.7: The Curves version of the timeline

布尔属性在线性曲线上进行插值。所有其他属性(因为它们是浮点数的组合)都沿缓入缓出曲线进行插值。您可以通过调整手柄来调整这些通过右键单击关键帧,可以查看关键帧处的切线

Boolean properties are interpolated on a linear curve. All other properties (since they are a combination of floats) are interpolated along an ease-in-out curve. You can adjust these by adjusting the handles of the tangents at the keyframes by right-clicking on the keyframe.

通常,这些缓入缓出曲线会导致您的 UI 出现弹性,而调整插值曲线可以消除这种弹性。例如,在两点之间为对象设置动画可能会使对象暂时越过目标点,这是由于默认插值曲线的缓入缓出特性

Often, these ease-in-out curves will cause your UI to appear bouncy, and adjusting the interpolation curve can remove that bounce. For example, animating an object between two points may make the object go past the destination point momentarily due to the ease-in-out nature of the default interpolation curve.

一个imation 活动

Animation Events

我最喜欢的东西之一关于动画剪辑,可以向时间线上的帧添加动画事件。动画事件允许您调用现有的函数在指定帧的 GameObject 上。它们由时间线上的白色旗帜表示,将鼠标悬停在它们上面将显示动画事件调用的函数的名称

One of my favorite things about Animation Clips is the ability to add Animation Events to frames on the timeline. Animation Events allow you to call functions that exist on the GameObject at specified frames. They are represented by white flags above the timeline, and hovering over them will show the name of the function called by the Animation Event.

动画事件只能调用动画剪辑所附加到的游戏对象上某处的函数。该函数可以是公共的或私有的,也可以有一个参数。参数可以是以下类型:

An Animation Event can only call a function that exists somewhere on the GameObject the Animation Clip is attached to. The function can be public or private and can also have a parameter. The parameter can be of the following types:

  • 漂浮
  • float
  • 整数
  • int
  • 细绳
  • string
  • 对象引用
  • An object reference
  • AnimationEvent对象
  • An AnimationEvent object

您可以通过右键单击要放置动画事件的帧上方的区域并单击“添加动画事件” ,将动画事件添加到动画剪辑的时间轴,或者您可以单击添加事件按钮。选择添加事件按钮将在播放头当前所在的位置添加动画事件

You can add an Animation Event to the Animation Clip’s timeline by right-clicking on the area above the frame in which you wish to place the Animation Event and clicking on Add Animation Event, or you can click the Add Event button. Selecting the Add Event button will add the Animation Event wherever the playhead currently rests.

图 14.8:动画事件

图 14.8:动画事件

Figure 14.8: Animation Events

您可以通过选择白旗并单击“删除”来删除动画事件,您可以通过单击并拖动它来移动它。

You can delete the Animation Event by selecting the white flag and clicking on Delete, and you can move it by clicking on it and dragging it.

的外观Animation Event 的 Inspector 取决于动画剪辑是否附加到 GameObject 以及 GameObject 当前是否被选中。两种外观如下图所示:

The appearance of the Animation Event’s Inspector depends on whether the Animation Clip is attached to a GameObject and whether the GameObject is currently selected. The two appearances are shown in the following screenshot:

图 14.9:动画事件检查器

图 14.9:动画事件检查器

Figure 14.9: The Inspector of the Animation Event

如果动画剪辑附加到游戏对象并且选择了游戏对象,则会出现一个下拉菜单,其中列出了所有可用的函数。如果所选函数有参数,则将提供与该参数有关的选项(请查看前面的屏幕截图)。否则,必须输入函数的名称和要传递的参数。手动输入

If the Animation Clip is attached to a GameObject and the GameObject is selected, a dropdown menu will appear with a list of all available functions. If the selected function has a parameter, then options concerning the parameter will be made available (take a look at the preceding screenshot). Otherwise, the name of the function and the parameters to pass will have to be entered manually.

如果您在启用滑块动画示例游戏对象的情况下播放第 14 章场景,您将看到一个正在播放事件的动画。

If you play the Chapter14 scene with the Slider Animation Example GameObject enabled, you will see an animation with events playing out.

现在我们已经看过了动画剪辑,我们来看看动画控制器

Now that we’ve looked at Animation Clips, let’s look at Animation Controllers.

动画控制器

Animator Controller

每当动画为对象创建剪辑时,会自动为其创建动画器(如果尚不存在)。动画器组件也会自动添加到对象中。当我在上一节中在Slider游戏对象上创建SliderExampleAnimation动画剪辑时,会创建一个名为Slider 的动画器,并将动画器组件附加到Slider游戏对象上:

Whenever an Animation Clip is created for an object, an Animator is automatically created for it (if one does not already exist). An Animator component is also automatically added to the object. When I created the SliderExampleAnimation Animation Clip on the Slider GameObject in the preceding section, an Animator named Slider was created and the Animator component was attached to the Slider GameObject:

图 14.10:滑块动画器的检查器

图 14.10:滑块动画器的检查器

Figure 14.10: The Slider Animator’s Inspector

需要一个动画器来播放动画剪辑,因为它决定何时播放动画剪辑。

An Animator is needed to play Animation Clips because it determines when Animation Clips are played.

Animator 是一种决策树又称为状态机。它包含一系列状态。状态本质上是某一时刻的状态。状态机的当前状态将表示此刻正在发生的事情。例如,如果有一个状态机描述我的操作和行为,那么我的当前状态就是在键盘上打字。我的状态机将具有我最终可以转换到的其他状态,例如在某些条件下睡觉为临近最后期限而哭泣得到满足。

An Animator is a type of decision tree known as a state machine. It holds a collection of states. States are essentially statuses at a moment in time. The current state of a state machine would be a representation of what is happening at this moment. So, for example, if there were a state machine describing my actions and behaviors, my current state would be typing on the keyboard. My state machine would have other states that I could eventually transition to, such as sleeping or crying about approaching deadlines if certain conditions are met.

Animator 中的状态由矩形称为节点。状态通过箭头线表示的转换连接。这些转换在预定时间或一组条件满足后发生。当前状态将有一个蓝色的动画状态栏,告诉您已完成的状态百分比。如果当前状态正在等待转换发生,则此状态栏可能会循环或停止在完整位置,直到满足转换条件:

States in the Animator are represented by rectangles called nodes. States are connected by transitions that are represented by arrow lines. These transitions occur after either a predetermined time or a set of conditions have been met. The current state will have a blue, animated status bar on it telling you the percentage of the state that has been completed. If the current state is waiting for a transition to occur, this status bar may loop or stop in the full position until the conditions of the transition are met:

图 14.11:滑块动画器

图 14.11:滑块动画器

Figure 14.11: The Slider Animator

动画器窗口的右下角显示当前动画器控制器的名称及其文件夹位置。当您有许多不同的动画器时,这会非常有用,因为它会告诉您正在使用哪个动画器。

The bottom-right corner of the Animator Window displays the name of the current Animator controller as well as its folder location. This can be very helpful when you have many different Animators, as it tells you which Animator you are working with.

状态可以是空的,或者表示动画剪辑。如果某个状态表示动画剪辑,则它将在其Inspector中将动画剪辑设置为其Motion属性,如以下屏幕截图所示:

States can be empty or represent Animation Clips. If a state represents an Animation Clip, it will have an Animation Clip set to its Motion property in its Inspector, as in the following screenshot:

图 14.12:动画状态的运动属性

图 14.12:动画状态的运动属性

Figure 14.12: The Motion property of an Animation State

大多数状态将以灰色显示,但其他颜色的状态将代表特殊状态。每个动画器都将有一个入口节点(绿色)、一个出口节点(红色)和一个任意状态节点(蓝色)。您添加到动画器的第一个状态将被分配默认图层状态节点(橙色)。您可以随时更改默认图层状态的状态。请注意,动画器图层将在后面的部分中讨论

Most states will be colored gray, but those colored otherwise will represent special states. Every Animator will have an Entry node (green), an Exit node (red), and an Any State node (blue) within it. The first state you add to an Animator will be assigned the Default Layer State node (orange). You can change the state of the Default Layer State at any time. Note that Animator Layers are discussed in a later section.

入口节点和出口节点本质上充当状态机之间的。您可以在状态机内设置状态机,这些分别决定进入和退出状态机后会发生什么。因此,入口节点表示状态机启动的实例,而出口节点表示状态机停止的实例

The Entry and Exit nodes essentially work as gates between state machines. You can have state machines within state machines, and these gates decide what happens after the state machine is entered and exited, respectively. So, the Entry node represents the instance the state machine starts, and the Exit node represents the instance it stops.

Entry节点始终会过渡到 Default Layer State,并且您无法定义过渡的条件,因此过渡将始终自动且立即发生。因此,您可以将 Default Layer State 视为状态机启动时将发生的第一个状态。

The Entry node always transitions to the Default Layer State, and you cannot define the conditions of the transition, so the transition will always happen automatically and instantly. Therefore, you can think of the Default Layer State as the first state that will occur when the state machine begins.

任何状态节点都是一个包罗万象的状态。当您想要发生转换时,可以使用此状态,无论当前状态。您只能从任何状态节点转移。继续使用描述我的行为的状态机示例,我将从任何状态转移到因截止日期临近而哭泣的状态,因为无论我现在正在做什么,如果满足“截止日期在 24 小时内”的条件,我都会泪流满面。

The Any State node is an all-encompassing state. You use this state when you want a transition to happen, regardless of the current state. You can only transition away from the Any State node. Continuing with the example of a state machine that describes my behavior, I would have a transition from Any State to the state of crying about approaching deadlines, because no matter what I am currently doing, I could burst into tears if the condition “deadline is within 24 hours” is met.

如前所述,动画器将保持当前状态,直到经过指定的时间或满足一组条件。选择过渡箭头将显示过渡条件:

As stated before, the Animator will stay in the current state until a specified amount of time has passed or a set of conditions have been met. Selecting a transition arrow will display the conditions of transition:

图 14.13:动画的扩展属性

图 14.13:动画的扩展属性

Figure 14.13: Expanded properties of an Animation

上面的截图需要指定的时间和条件。转换也不是瞬间完成的,需要0.25才能完成。

The transition from the preceding screenshot requires both a specified amount of time and a condition. The transition also isn’t instantaneous and takes 0.25 s to complete.

发生转换所必须满足的条件由动画器的参数设置,可以在动画器窗口的左上角找到和创建

The Conditions that must be met for a transition to occur are set by the Animator’s Parameters, which can be found and created in the top-left corner of the Animator Window:

图 14.14:添加动画器参数

图 14.14:添加动画器参数

Figure 14.14: Adding an Animator Parameter

这些参数的值可以从脚本中设置。参数有四种类型:FloatIntBoolTrigger。前三种以其值类型命名,但Trigger不太明显。Trigger 是一个 Bool 参数,在转换使用后会立即重置为False。触发器参数有助于创建洪水门类型动画必须停止并等待才能进入下一个状态的操作。在状态和转换形成循环的情况下,这些操作比布尔参数更受欢迎,因为布尔参数必须在状态循环回来之前手动重置。

The values of these parameters can be set from scripts. There are four types of Parameters: Float, Int, Bool, and Trigger. The first three are named for their value type, but a Trigger is a little less obvious. A Trigger is a Bool Parameter that instantly resets itself to False after it is used by a transition. Trigger Parameters are helpful for creating flood-gate-type actions where the animation has to stop and wait before it can proceed to the next state. These are preferred over Bool Parameters in instances where the states and transitions form a loop, because a Bool Parameter would have to be manually reset before the state looped back around.

查看参数列表时,可以通过右侧的值判断它们的类型。浮点型参数有小数,整型参数有整数,布尔型参数有方形复选框,触发器参数有圆形单选按钮。

When you look at the list of Parameters, you can tell which type they are by the value on the right. Float Parameters have decimal numbers, Int Parameters have integers, Bool Parameters have square checkboxes, and Trigger Parameters have circular radio buttons.

由于 Animators 是状态机,因此它们除了动画之外还能完成很多其他任务。Animators 还可用于跟踪复杂的游戏逻辑。例如,我为一款三消 RPG 游戏创建了以下状态机,以跟踪游戏中当前发生的情况。使用它来跟踪游戏的当前状态,我可以根据游戏中发生的情况限制玩家可以做的事情。

Since Animators are state machines, they can be used to accomplish much more than animations. Animators can be used to keep track of complex game logic. For example, I created the following state machine for a match-three RPG to keep track of what was currently happening in the game. Using it to keep track of the current state of the game allowed me to restrict what the player could do based on what was happening in the game.

例如,如果敌方角色正在攻击,玩家将不会与棋盘上的棋子互动:

For example, if the enemy character was attacking, the player would not interact with the pieces on the board:

图 14.15:用于逻辑的动画状态机示例

图 14.15:用于逻辑的动画状态机示例

Figure 14.15: Example of a Animation State Machine used for logic

现在我们已经回顾了Animator Controller 的属性,让我们看看它的一些用法塞斯。

Now that we’ve reviewed the properties of the Animator Controller, let’s look at some of its uses.

过渡动画的动画师

The Animator of Transition Animations

第 9 章中,我们了解了按钮动画过渡并为按钮创建了一个简单的动画。我们让按钮组件自动为我们生成动画,但从未查看过动画或用它做任何事情。现在我们已经讨论了动画,让我们看看保存Assets/Animations中的播放按钮的动画

In Chapter 9, we took a look at Button Animation transitions and created a simple animation for a Button. We let the Button component automatically generate the Animator for us, but never looked at the Animator or did anything with it. Now that we’ve discussed Animators, let’s look at the Animator of the Play Button saved in Assets/Animations:

图 14.16:按钮的动画器

图 14.16:按钮的动画器

Figure 14.16: The Animator of a Button

从上面的截图中可以看出,为我们自动生成的 Animator 并不特别复杂,其设置也是不言自明的。它包含以下五个动画剪辑的状态:NormalHighlightedPressedSelectedDisabled 。所有动画剪辑都从Any State过渡。此外,还有五个触发参数正常高亮按下选中禁用

As you can see from the preceding screenshot, the Animator that was automatically generated for us isn’t particularly complicated and its setup is self-explanatory. It contains states to hold the following five Animation Clips: Normal, Highlighted, Pressed, Selected, and Disabled. All of the Animation Clips transition from Any State. Also, there are five Trigger Parameters: Normal, Highlighted, Pressed, Selected, and Disabled.

为允许过渡动画的任何 UI 元素自动生成动画器将导致完全相同的设置。尽管此动画器已为您预设,但您可以随意调整合身。

Automatically generating an Animator for any of the UI elements that allow for transition animations will result in the exact same setup. Even though this Animator is preset for you, you are free to adjust it however you see fit.

动画图层

Animator layers

使用 Animator 时,如果你如果某个状态可以转换到多个节点,则只能发生一次转换。例如,在下面的屏幕截图中,ChooseAState状态一次只能转换到其他状态之一,即使满足所有转换条件也是如此;无论您使用哪种类型的参数,情况都是如此

When using the Animator, if you have a state with transitions to multiple nodes, only one transition can occur. For example, in the following screenshot, the ChooseAState state can only transition to one of the other states at once, even if the transition conditions for all are met; this is true regardless of the type of Parameter that you use:

图 14.17:第 14 章场景中的分叉动画示例

图 14.17:第 14 章场景中的分叉动画示例

Figure 14.17: A forking animation example in the Chapter14 scene

如果你想要多个动画一次触发,您可以使用动画图层。以下图层设置将同时运行所有三个状态:

If you want multiple animations to trigger at once, you can use Animation Layers. The following layer setup will have all three states running simultaneously:

图 14.18:第 14 章场景中的动画图层示例

图 14.18:第 14 章场景中的动画图层示例

Figure 14.18: An Animation Layers Example in the Chapter14 scene

我发现最常见的需求是当你有一个由多个精灵表组成的对象,并且你想同时触发多个精灵表动画,而把它们都放在同一个动画剪辑上是没有意义的。例如,我曾经开发过一款游戏,其中的 2D 角色有多个可互换的部分,每个部分都有自己的精灵表动画。有必要让每个部分的空闲动画同时开始。由于这些部分可以互换,因此可以实现多个部分的组合,而制作所有不同的空闲动画组合是没有意义的。给每个可能的部分一个自己的动画器也是没有意义的。所以,我为每个身体部位制作了一个图层,并能够让各个精灵动画同时播放时间。

I’ve found that the most common need for something like this is when you have an object made of multiple sprite sheets and you want multiple sprite sheet animations to trigger at the same time and putting them all on the same Animation Clip doesn’t make sense. For example, I’ve worked on a game where a 2D character had multiple interchangeable parts, and each part had its own sprite sheet animation. It was necessary to have the idle animation for each part start all at the same time. Since the parts could be swapped out, there were multiple combinations of parts that could be achieved, and it would not have made sense to make all the different possible idle animation combinations. It also wouldn’t have made sense to give each possible part its own Animator. So, I made a layer for each body part and was able to have the individual sprite animations all play at the same time.

在脚本中设置动画参数

Setting Animation Parameters in scripts

您可以使用Animator类的SetFloat()SetInteger()SetBool()SetTrigger()ResetTrigger()函数通过脚本设置 Animator 参数的值。您可以参考Animator 参数变量通过在Animator 中分​​配给它们的字符串名称来表示。

You can set the values of the Animator Parameters via scripts using the SetFloat(), SetInteger(), SetBool(), SetTrigger(), and ResetTrigger() functions of the Animator class. You reference the Animator Parameter variables by the string names assigned to them within the Animator.

要设置动画参数,首先要获取定义参数的动画器;可以使用公共动画器变量或使用GetComponent<Animator>()执行此操作。然后,在动画器上调用必要的函数。

To set the Animation Parameters, you first get the Animator on which the Parameters were defined; you can do this with either a public Animator variable or using GetComponent<Animator>(). Then, you call the necessary function on the Animator.

让我们看一个设置以下参数的示例:

Let’s look at an example that would set the following Parameters:

图 14.19:不同类型的动画参数

图 14.19:不同类型的动画参数

Figure 14.19: Different types of Animation Parameters

以下脚本将设置上一个屏幕截图中定义的动画参数

The following script would set the Animator Parameters defined in the previous screenshot:

使用System.Collections;
使用 System.Collections.Generic;
使用 UnityEngine;
公共类第 14 章示例:MonoBehaviour
{
    动画师 theAnimator;
    无效唤醒()
    {
        theAnimator = GetComponent<Animator>();
    }
    公共无效设置动画参数()
    {
        theAnimator.SetFloat("Float参数", 1.0f);
        theAnimator.SetInteger("Int参数", 1);
        theAnimator.SetBool("Bool参数", true);
        theAnimator.SetTrigger("TriggerParameter"); // 设置为 true
        theAnimator.ResetTrigger("TriggerParameter"); // 设置为 false
    }
    公共无效示例函数()
    {
        Debug.Log("动画事件没有发送参数");
    }
    公共无效示例参数函数(int 值)
    {
        Debug.Log("动画事件发送以下值:" + value);
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Chapter14Examples : MonoBehaviour
{
    Animator theAnimator;
    void Awake()
    {
        theAnimator = GetComponent<Animator>();
    }
    public void SetAnimatorParameters()
    {
        theAnimator.SetFloat("FloatParameter", 1.0f);
        theAnimator.SetInteger("IntParameter", 1);
        theAnimator.SetBool("BoolParameter", true);
        theAnimator.SetTrigger("TriggerParameter"); // sets to true
        theAnimator.ResetTrigger("TriggerParameter"); // sets to false
    }
    public void ExampleFunction()
    {
        Debug.Log("The Animation Event is not sending an argument");
    }
    public void ExampleParameterFunction(int value)
    {
        Debug.Log("The Animation Event sends the following value: " + value);
    }
}

使用的好处触发器的优点在于,您通常不需要重置它,因为它会在转换使用它时立即重置。但是,如果您设置了触发器并且转换从未达到,则需要使用ResetTrig重置它ger()

The benefit of using a Trigger is that you usually don’t have to reset it as it instantly resets the moment a transition uses it. However, if you set a Trigger and the transition is never reached, you will need to reset it using ResetTrigger().

动画师行为

Animator Behaviours

如果你想编写在状态中的特定点触发的代码,你可以使用一种称为 State 的独特脚本类机器行为。状态机器行为可以添加到您在 Animator 中创建的任何状态节点。我指定您创建,因为您无法将它们添加到入口节点、出口节点或任何状态节点。

If you want to write code that fires at specific points within a state, you can use a unique class of scripts known as State Machine Behaviours. State Machine Behaviours can be added to any state node you create within the Animator. I specify you create because you cannot add them to the Entry node, Exit node, or Any State node.

您可以通过选择一个状态并单击“添加行为”来创建新的状态机行为:

You can create a new State Machine Behaviour by selecting a state and clicking on Add Behaviour:

图 14.20:如何向动画状态添加行为

图 14.20:如何向动画状态添加行为

Figure 14.20: How to add a Behaviour to an Animation State

全新状态机以这种方式创建的行为保存在Assets文件夹中。

All new State Machine Behaviours created in this way are saved in the Assets folder.

当您打开脚本时,它将自动填充以下代码:

When you open the script, it will be automatically populated with the following code:

使用System.Collections;
使用 System.Collections.Generic;
使用 UnityEngine;
公共类 ChooseAStateBehaviour:StateMachineBehaviour
{
    // 当转换开始并且状态机开始评估此状态时,将调用 OnStateEnter
    //覆盖
    公共覆盖void OnStateEnter(Animator动画师,AnimatorStateInfo stateInfo,int layerIndex)
    {
        // 在此实现状态进入逻辑
    }
    // 在 OnStateEnter 和 OnStateExit 回调之间,每个更新帧都会调用 OnStateUpdate
    //覆盖
    公共覆盖void OnStateUpdate(Animator动画师,AnimatorStateInfo stateInfo,int layerIndex)
    {
        // 在此实现状态更新逻辑
    }
    // 当转换结束且状态机完成评估此状态时,调用 OnStateExit
    //覆盖
    公共覆盖void OnStateExit(Animator动画师,AnimatorStateInfo stateInfo,int layerIndex)
    {
        // 在此实现状态退出逻辑
    }
    // OnStateMove 在 Animator.OnAnimatorMove() 之后立即调用
    //覆盖
    公共覆盖void OnStateMove(Animator动画师,AnimatorStateInfo stateInfo,int layerIndex)
    {
        // 实现处理和影响根运动的代码
    }
    // OnStateIK 在 Animator.OnAnimatorIK() 之后立即调用
    //覆盖
    公共覆盖void OnStateIK(Animator动画师,AnimatorStateInfo stateInfo,int layerIndex)
    {
        // 实现设置动画 IK(逆运动学)的代码
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ChooseAStateBehaviour : StateMachineBehaviour
{
    // OnStateEnter is called when a transition starts and the state machine starts to evaluate this state
    //override
    public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        // Implement state enter logic here
    }
    // OnStateUpdate is called on each Update frame between OnStateEnter and OnStateExit callbacks
    //override
    public override void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        // Implement state update logic here
    }
    // OnStateExit is called when a transition ends and the state machine finishes evaluating this state
    //override
    public override void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        // Implement state exit logic here
    }
    // OnStateMove is called right after Animator.OnAnimatorMove()
    //override
    public override void OnStateMove(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        // Implement code that processes and affects root motion
    }
    // OnStateIK is called right after Animator.OnAnimatorIK()
    //override
    public override void OnStateIK(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        // Implement code that sets up animation IK (inverse kinematics)
    }
}

请注意,此类派生自StateMachineBehaviour而不是MonoBehaviour,就像我们附加到 GameObjects 的脚本一样

Note that this class is derived from StateMachineBehaviour rather than MonoBehaviour, like the scripts we attach to GameObjects.

脚本中预先为您编写了一些函数,并附有使用方法的说明。正如Awake()Start()Update()是 MonoBehaviour 的预定义函数一样,OnStateEnter()OnStateUpdate()OnStateExit()OnStateIK()OnStateMove()也是在特定时间调用的预定义函数。您可以删除不想使用的任何函数。您还可以编写其他该脚本中的函数并不局限于这些预定义的函数。

There are a few functions prewritten in the script for you, along with descriptions of how to use them. Just as Awake(), Start(), and Update() are predefined functions for MonoBehaviour, OnStateEnter(), OnStateUpdate(), OnStateExit(), OnStateIK(), and OnStateMove() are predefined functions that call at specific times. You can delete whichever functions you don’t want to use. You can also write other functions within this script, and they are not restricted to these predefined ones.

这些函数可以做任何你想做的事情,甚至可以设置你的动画器的动画器参数。

These functions can do whatever you want them to do, even set the Animator Parameters of your Animator.

我发现状态机行为非常有用,因为我广泛使用状态机来控制游戏逻辑。在本节前面,我向您展示了我为三消 RPG 创建的状态机。我使用多个状态机行为来让我的其他脚本知道状态何时发生变化,在特定时间调用其他脚本中的函数等等

I find State Machine Behaviours to be incredibly helpful because I use state machines extensively to control the logic of my games. Earlier, in this section, I showed you a state machine I created for a match-three RPG. I used multiple State Machine Behaviours to let my other scripts know when the states had changed, call functions from other scripts at specified times, and so on.

现在我们已经回顾了如何使用动画剪辑和动画控制器,让我们看一些如何在我们的中实现它们项目。

Now that we’ve reviewed using Animation clips and Animator Controllers, let’s look at some examples of how to implement them in our project.

示例

Examples

本章的主要重点是提供如何创建常见 UI 动画和效果的示例,让我们开始吧。在这些示例中,我将向您展示一个基本的可重复使用的动画,用于淡入淡出 UI 面板,以及一个更复杂的依赖于玩家兴趣的战利品盒动画反应。

The main focus of this chapter is to provide examples of how to create common UI animations and effects, so let’s get to it. In these examples, I will show you a basic re-usable animation for fading UI Panels in and out, as well as a more complex animation of a loot box dependent on player interaction.

动画弹出窗口淡入淡出

Animating pop-up windows to fade in and out

通过我们的第一个例子,我们将继续致力于我们的主要场景。

With our first example, we will continue to work on our main scene.

目前,我们有一个暂停面板库存面板,当按下PI键时会立即出现。这不是很有趣,所以让我们添加一些动画,让面板通过淡入淡出和缩放动画弹出和隐藏

Currently, we have a Pause Panel and Inventory Panel that instantly appear when the P or I key is pressed. That’s not terribly interesting, so let’s add some animations to have the Panels pop in and out with fade and scale animations.

每次进入新场景时,都必须展开所有父级才能看到其子级,这可能会变得相当繁琐。打开所有父级的快捷方式是选择层次结构中的所有内容,然后按键盘上的右箭头键。如果有多个嵌套,您可以多次执行此操作。左箭头键将折叠父级。

It can get rather tedious having to expand all of the parents to see their children every time you go to a new scene. A shortcut to open all parents is to select everything in the Hierarchy and then press the right arrow key on the keyboard. You can do this multiple times if you have multiple nestings. The left arrow key will collapse the parents.

我们设置这些动画及其功能的工作流程是创建动画剪辑、设置动画器,然后编写代码在适当的时间设置动画器的参数。为了使这些步骤更容易理解,我将它们分成几个部分。第一部分涵盖设置动画剪辑和动画器所涉及的所有步骤,第二部分涵盖编写动画所需的设置代码。

Our workflow to set up these animations and their functionality will be to create Animation Clips, set up our Animator, and then write code that sets the Animator’s parameters at the appropriate times. To make the steps easier to digest, I’ve broken them into sections. The first section covers all of the steps involved with setting up the Animation Clips and Animator and the second section covers the sets involved with writing code.

设置动画

Setting up the animations

要在暂停面板库存面板上创建弹出动画,请执行 以下步骤:

To create a pop in and out animation on Pause Panel and Inventory Panel, perform the following steps:

  1. 我们首先将动画剪辑添加到暂停面板,使其缩放并淡入。打开动画窗口并从层次结构中选择暂停面板。选择创建以添加新的动画剪辑。将新的动画剪辑保存在Assets/Animation文件夹中并将命名为FadeAndScale
  2. We’ll start by adding an Animation Clip to Pause Panel that will cause it to scale and fade in. Open the Animation Window and select Pause Panel from the Hierarchy. Select Create to add a new Animation Clip. Save the new Animation Clip in the Assets/Animation folder and name it FadeAndScale.
  3. 我们希望控制此面板的四个属性:其比例、其 alpha 值、其交互能力以及其光线投射阻挡。让我们从比例开始。我们可以从 Rect Transform 组件调整此属性。选择“添加属性”,然后单击单击Rect TransformScale旁边的加号,如下所示:
  4. We want to control four properties of this Panel: its scale, its alpha value, its ability to be interacted with, and its raycast blocking. Let’s start with scale. We can adjust this property from the Rect Transform component. Select Add Property and click on the plus sign next to Scale under Rect Transform, as follows:
图 14.21:添加比例属性

图 14.21:添加比例属性

Figure 14.21: Adding the Scale property

  1. 在0:001:00处为Scale属性初始化了两个关键帧。这意味着动画将持续一秒钟,对于弹出动画来说,这有点长。选择1:00处的关键帧并将其拖动到0:30处,使动画持续半秒:
  2. Two keyframes are initialized for the Scale property at 0:00 and 1:00. This means that the animation will last one second, which is a bit long for a pop-in animation. Select the keyframe at 1:00 and drag it to 0:30 to make the animation half a second long:
图 14.22:调整动画的长度

图 14.22:调整动画的长度

Figure 14.22: Adjusting the length of the animation

  1. 我们希望面板从小开始,然后变大。为此,我们需要调整xy比例。展开Scale属性(选择Pause Panel : Scale旁边的箭头)以查看Scalexyz属性。目前,所有三个坐标的比例在两个关键帧处都设置为1。由于我们希望比例从小开始,然后放大到正常大小,我们希望它从0缩放到1。我们只需调整xy比例,因为z比例实际上不会影响 2D 对象。因此,在关键帧0:00处,通过在属性框中输入0将Scale.xScale.y属性更改为0 ,如下所示:
    图 14.23:调整比例属性

    图 14.23:调整比例属性

    如果播放动画,您会注意到暂停面板正在快速缩小。由于父/子关系会使子级随其父级缩放,因此我们不必分别为所有暂停面板 子级的缩放设置动画。

  2. We want the Panel to start off small and then get big. To achieve this, we will need to adjust the x and y scales. Expand the Scale property (select the arrow next to Pause Panel : Scale) to view the x, y, and z properties of Scale. Currently, the scale for all three coordinates is set to 1 at both keyframes. Since we want the scale to start small and then enlarge to its normal size, we want it to scale from 0 to 1. We only need to adjust the x and y scales since the z scale doesn’t really affect a 2D object. So, at keyframe 0:00, change the Scale.x and Scale.y properties to 0 by typing 0 in their property boxes, as follows:

    Figure 14.23: Adjusting the Scale properties

    If you play the animation, you’ll note that Pause Panel is quickly scaling in. Since parent/child relationships scale children with their parents, we don’t have to animate the scale of all the Pause Panel children separately.

  1. 现在,让我们控制 Panel 的 alpha 值以使其淡入。我们不想对Pause Panel的 Image 组件的Color属性上的 alpha 值进行动画处理。这样做只会影响 Panel 背景图像的 alpha 值,而不会影响子项。相反,我们想要对Canvas Group组件上的 alpha 值进行动画处理。这将使Pause Panel及其所有子项的 alpha 值协同工作。请记住,这正是我们一开始使用 Canvas Group 组件的全部原因。让我们选择Add Property | Canvas Group | Alpha
  2. Now, let’s control the alpha of the Panel to fade it in. We don’t want to animate the alpha on the Color property of the Image component of Pause Panel. Doing so would only affect the alpha of the Panel’s background Image and would not affect the children. Instead, we want to animate the alpha value on the Canvas Group component. This will make the alpha of the Pause Panel and all its children work in unison. Remember that this was the whole reason we used a Canvas Group component to begin with. Let’s select Add Property | Canvas Group | Alpha:
图 14.24:添加 Alpha 属性

图 14.24:添加 Alpha 属性

Figure 14.24: Adding the Alpha property

  1. 要淡入,Panel 应该从完全透明开始,到完全不透明结束。两个关键帧都已将CanvasGroup.Alpha属性设置为1,因此在第一个关键帧处将CanvasGroup.Alpha属性更改为0。播放动画(或拖动播放头将显示面板及其所有子项淡入。
  2. To fade in, the Panel should start completely transparent and end completely opaque. Both keyframes already have the CanvasGroup.Alpha property set to 1, so change the CanvasGroup.Alpha property to 0 at the first keyframe. Playing the animation (or scrubbing the playhead) will show the Panel and all of its children fading in.
  3. 我们需要动画来显示面板是否可以交互。我们不希望玩家在面板弹出时能够与下拉菜单或静音按钮交互。这也由 Canvas Group 组件控制 - 选择Add Property | Canvas Group | Interactable
  4. We will need to animate whether or not the Panel can be interacted with. We don’t want the player to be able to interact with the dropdown menu or mute buttons when the Panel is still popping in. This too is controlled by the Canvas Group component—select Add Property | Canvas Group | Interactable.
  5. 只有在Pause Panel完全弹出到场景中后,其子项才应可交互。因此,在第一个关键帧处取消选中CanvasGroup.Interactable旁边的复选框,以使 Canvas Group 从可交互动画变为不可交互。当您拖动播放头时,您会看到该属性直到最后一帧才会重新启用
  6. The Pause Panel and its children should be interactable only after it has fully popped into the scene. So, deselect the checkbox next to CanvasGroup.Interactable at the first keyframe to make the Canvas Group animate from interactable to not interactable. When you scrub the playhead, you’ll see that the property does not turn back on until the very last frame.
  7. 我们需要动画化的最后一项是它的射线投射阻塞。选择添加属性|画布组|射线投射阻塞。像对Interactable属性所做的那样,将其从false动画化为true。您的FadeAndScale动画剪辑的时间线现在应该显示如下:
  8. The last item we will need to animate is its raycast blocking. Select Add Property | Canvas Group | Blocks Raycast. Animate it from false to true as you did with the Interactable property. Your FadeAndScale Animation Clip’s timeline should now appear, as follows:
图 14.25:FadeAndScale 动画剪辑的时间轴

图 14.25:FadeAndScale 动画剪辑的时间轴

Figure 14.25: The Timeline of the FadeAndScale Animation Clip

  1. 现在我们有了设置动画剪辑后,我们可以开始在 Animator 中工作。当我们创建FadeAndScale动画剪辑时,会自动创建一个名为“Pause Panel”的 Animator 。还将 Animator 组件添加到“Pause Panel”游戏对象,并将“Pause Panel”动画器分配给它。

    选择暂停面板后,打开动画窗口。您应该看到类似以下内容:

    图 14.26:暂停面板的动画器

    图 14.26:暂停面板的动画器

    您将看到一个名为FadeAndScale的状态。此状态使用FadeAndScale动画剪辑作为其运动,您可以在其 Inspector 中查看。

  2. Now that we have our Animation Clip set up, we can start working inside the Animator. When we created the FadeAndScale Animation Clip, an Animator named Pause Panel was automatically created. An Animator component was also added to the Pause Panel GameObject, with the Pause Panel Animator assigned to it.

    With Pause Panel selected, open the Animator Window. You should see something similar to the following:

    Figure 14.26: The Animator of Pause Panel

    You will see a state named FadeAndScale. This state uses the FadeAndScale Animation Clip as its Motion, which you can view in its Inspector.

  1. 目前,由于FadeAndScale状态已连接到Entry节点并设置为 Layer Default状态,当我们玩游戏时,FadeAndScale动画将立即播放。它还会循环播放。显然,这根本不是我们想要的。让我们通过创建一个空状态作为图层默认状态来在游戏开始时停止播放。在动画器中的任意位置单击鼠标右键,然后选择“创建状态|空”
    图 14.27:创建空状态

    图 14.27:创建空状态

    这将添加灰色将名为New State 的状态添加到Animator。

  2. Currently, since the FadeAndScale state is connected to the Entry node and set as the Layer Default State, when we play the game, the FadeAndScale animation will play instantly. It will also play on a loop. That’s not at all what we want, obviously. Let’s stop it from playing when the game starts by creating an empty state as the Layer Default State. Right-click anywhere within the Animator and select Create State | Empty:

    Figure 14.27: Creating an Empty State

    This will add a gray-colored state named New State to the Animator.

  1. 通过在检查器 (Inspector)中更改其名称,将新状态 (New State)重命名为空状态 (Empty State) 。
  2. Rename New State to Empty State by changing its name in its Inspector.
  3. 通过右键单击并选择“设置为图层默认状态”将空状态设置为图层默认状态
    图 14.28:将状态设置为图层默认值

    图 14.28:将状态设置为图层默认值

    现在它将通过转换连接到Entry。FadeAndScale现在也不再是 Layer 默认状态,并且不会有任何转换与其连接:

    图 14.29:将空状态设置为图层默认值

    图 14.29:将空状态设置为图层默认值

    我们使用空状态作为我们的图层默认状态,因为我们希望面板不执行任何操作,而等待我们告诉它开始动画。

  4. Set Empty State to the Layer Default State by right-clicking on it and selecting Set as Layer Default State:

    Figure 14.28: Setting a state as the Layer Default

    It will now be connected to Entry via a transition. FadeAndScale will also now no longer be the Layer Default State and will not have any transitions connected to it:

    Figure 14.29: Empty State set as the Layer Default

    We used an empty state as our Layer Default State because we want the Panel to do nothing, while it waits for us to tell it to start animating.

  1. 重新排列项目,使其布局更加清晰。我个人比较喜欢这个动画器的以下布局:
  2. Rearrange the items to a more viewable layout. I personally like the following layout for this Animator:
图 14.30:重新排列各州

图 14.30:重新排列各州

Figure 14.30: Rearranging the States

  1. FadeAndScale动画剪辑现在不再在游戏开始时立即播放。但是,它仍设置为动画循环。要解决此问题,请从项目文件夹视图中选择FadeAndScale动画剪辑。这将调出其检查器。

    取消选择循环时间属性禁用动画循环:

  2. The FadeAndScale Animation Clip now no longer instantly plays when the game starts. It is still set to have its animation loop, however. To fix this, select the FadeAndScale Animation Clip from your Project folder view. This will bring up its Inspector.

    Deselect the Loop Time property to disable looping on the animation:

图 14.31:取消选择循环时间

图 14.31:取消选择循环时间

Figure 14.31: Deselecting Loop Time

  1. 我们希望暂停面板能够根据命令淡入淡出,但我们没有用于淡出的动画剪辑,只有一个用于淡入的动画剪辑。实际上,我们不需要创建整个动画剪辑来实现此动作。我们可以简单地向后播放 FadeAndScale 动画剪辑。选择FadeAndScale状态并按Ctrl + D复制它。这将为您提供一个名为FadeAndScale 0的新状态,该状态将FadeAndScale动画剪辑设置为其运动
  2. We want the Pause Panel to be able to fade in and out on command, but we don’t have an Animation Clip for fading out, only one for fading in. We actually don’t need to create a whole Animation Clip to achieve this motion. We can simply play the FadeAndScale Animation Clip backward. Duplicate the FadeAndScale state by selecting it and pressing Ctrl + D. This will give you a new state named FadeAndScale 0 that has the FadeAndScale Animation Clip set as its Motion:
图 14.32:复制状态

图 14.32:复制状态

Figure 14.32: Duplicating a State

  1. 将FadeAndScale状态重命名为FadeAndScaleIn,将FadeAndScale 0状态重命名FadeAndScaleOut
  2. Rename the FadeAndScale state to FadeAndScaleIn and the FadeAndScale 0 state to FadeAndScaleOut.
  3. 要设置FadeAndScaleOut状态以向后播放FadeAndScale动画剪辑,我们只需在检查器 (Inspector)中将其速度 (Speed)更改为-1
  4. To set the FadeAndScaleOut state to play the FadeAndScale Animation Clip backward, we simply have to change its Speed to -1 in its Inspector:
图 14.33:设置动画以反向播放

图 14.33:设置动画以反向播放

Figure 14.33: Setting an animation to play in reverse

  1. 现在我们已经正确设置了所有状态,我们可以在它们之间创建过渡。首先创建两个名为FadeIn FadeOut 的触发参数:
    图 14.34:动画器参数

    图 14.34:动画器参数

    我们在这里使用触发器参数,因为我们希望这些值在使用后立即重置。这样,我们就可以创建一个动画循环,而不必编写重置参数值的代码

  2. Now that we have all of our states set up properly, we can create the transitions between them. Start by creating two Trigger Parameters named FadeIn and FadeOut:

    Figure 14.34: The Animator Parameters

    We are using Trigger Parameters here because we want these values to instantly reset after we’ve used them. That way, we can create an animation cycle without having to write code that resets the parameter values.

  1. 您可以通过右键单击第一个状态、选择“进行转换”,然后单击第二个状态来创建状态之间的转换:
    图 14.35:进行转换

    图 14.35:进行转换

    按以下方式创建状态之间的转换

    图 14.36:最终的状态转换布局

    图 14.36:最终的状态转换布局

    此过渡流程将允许面板从无动画过渡到FadeAndScale动画剪辑,然后过渡到反向的FadeAndScale动画剪辑,并在两者之间来回过渡。

  2. You create transitions between states by right-clicking on the first state, selecting Make Transition, and then clicking on the second state:

    Figure 14.35: Making transitions

    Create transitions between the states in the following manner:

    Figure 14.36: The final state transition layout

    This transition flow will allow the Panel to transition from no animation to the FadeAndScale Animation Clip, then to the reversed FadeAndScale Animation Clip, and back and forth between the two.

  1. 如果你玩游戏,Pause Panel会立即从Empty State转到FadeAndScaleIn状态,然后转到FadeAndScaleOut状态,并在两者之间无限期地来回切换。这是因为转换会自动设置为在动画完成后发生。要停止这种情况,你必须告诉转换仅在设置参数后发生。选择Empty StateFadeAndScaleIn状态之间的转换。选择条件列表中的加号以添加新条件。确保条件设置为FadeIn Trigger。由于我们不想让时间成为转换的一个因素,因此取消选择Has Exit Time
  2. If you play the game, Pause Panel will instantly go from the Empty State to the FadeAndScaleIn state and then to the FadeAndScaleOut state, and back and forth between the two indefinitely. This is because transitions are automatically set to occur after an animation is complete. To stop this, you have to tell the transitions to only occur after a parameter has been set. Select the transition between the Empty State and the FadeAndScaleIn state. Select the plus sign in the Conditions list to add a new condition. Ensure that the condition is set to the FadeIn Trigger. Since we don’t want timing to be a factor in our transition, deselect Has Exit Time:
图 14.37:过渡属性

图 14.37:过渡属性

Figure 14.37: The transition properties

  1. 对FadeAndScaleInFadeAndScaleOut状态之间的转换完成相同的步骤。将FadeAndScaleIn状态到FadeAndScaleOut状态的转换条件设置为FadeOut触发器。将从FadeAndScaleOut状态过渡到FadeAndScaleIn状态,再到FadeIn触发器。此外,取消选择Fixed Duration并将所有值设置为0 ,以确保即时过渡:
  2. Complete the same steps for the transitions between the FadeAndScaleIn and FadeAndScaleOut States. Set the Condition for the transition from the FadeAndScaleIn State to the FadeAndScaleOut State to the FadeOut Trigger. Set the Condition for the transition from the FadeAndScaleOut state to FadeAndScaleIn State to the FadeIn Trigger. Additionally, deselect Fixed Duration and set all values to 0 to ensure an instant transition:
图 14.38:如何设置暂停面板的过渡

图 14.38:如何设置暂停面板的过渡

Figure 14.38: How to set up the transitions of the Pause Panel

  1. 如果你现在玩这个游戏,你会请注意,游戏开始时,暂停面板是可见的。这是因为我们的动画状态取代了我们在ShowHidePanels.cs中编写的代码,该代码使暂停面板在场景开始时不可见。我们稍后会处理损坏的代码,但现在,通过将其Canvas Group组件设置为具有以下值,使暂停面板在开始时不可见
    图 14.39:画布组的属性

    图 14.39:画布组的属性

    现在,当您玩游戏时,“暂停面板”将不会在开始时出现。

  2. If you play the game now, you’ll note that the Pause Panel is visible when the game starts. This is because our animation states supersede the code we wrote in ShowHidePanels.cs that made the Pause Panel invisible at the start of the scene. We’ll deal with our broken code later, but for now, make the Pause Panel invisible at start by setting its Canvas Group component to have the following values:

    Figure 14.39: The properties of the Canvas Group

    Now when you play the game, the Pause Panel will not appear at the start.

  1. 我们已经完成了暂停面板的动画设置,但在继续设置库存面板之前,请检查动画是否正常工作。为此,请安排您的窗口,以便游戏视图和动画器窗口都可见:
    图 14.40:播放空状态

    图 14.40:播放空状态

    您将看到“暂停面板”当前运行状态的进度条。要强制转换,请点击在相应触发参数旁边的圆圈上

  2. We’ve completed setting up the animations for the Pause Panel, but before you proceed to the Inventory Panel, check whether the animations are working correctly. To do so, arrange your windows so that your Game view and Animator Window are both visible:

    Figure 14.40: Playing the Empty State

    You’ll see the progress bar on the current state of Pause Panel running. To force the transitions, click on the circles next to the appropriate Trigger parameters.

  1. 如果动画工作正常,你现在可以在Inventory Panel上设置动画了。你可能会想,“呃,现在我必须为Inventory Panel再次做这些?!”但是不用担心,你不必再做了,因为你可以在Inventory Panel上重用 Animator 来处理Pause Panel 。我们在FadeAndScale动画剪辑中在Pause Panel上更改的所有属性也存在于Inventory Panel上。所以,为了给Inventory Panel提供相同的动画和控件,我们只需将创建的 Animator 附加到Inventory Panel上即可。

    由于我们将在两个不同的对象上使用我们创建的动画器,因此最好重命名它。将名称从Pause Panel更改为PopUpPanels。现在,将其拖到Inventory Panel上。

  2. If your animations are working the way they should, you can now set up the animations on the Inventory Panel. You may be thinking, “Ugh, now I have to do all of this again for the Inventory Panel?!” However, worry not, you don’t have to do it again, because you can reuse the Animator for the Pause Panel on the Inventory Panel. All of the properties we changed on the Pause Panel in the FadeAndScale Animation Clip also exist on the Inventory Panel. So, to give the same set of animations and controls to the Inventory Panel, we can simply attach the Animator we created to Inventory Panel.

    Since we will be using the Animator we created on two different objects, it is a good idea to rename it. Change the name from Pause Panel to PopUpPanels. Now, drag it onto the Inventory Panel.

  3. 正如我们必须更改暂停面板上的 Canvas Group 的属性以阻止它在场景开始时出现一样,我们还必须更改库存面板上的Canvas Group组件的属性。设置属性,如图14 .39所示。
  4. Just as we had to change the properties of the Canvas Group on Pause Panel to stop it from appearing when the scene starts, we also have to change the properties on the Canvas Group component of the Inventory Panel. Set the properties as they appear in Figure 14.39.

现在我们已经完成了两个面板的动画设置,我们可以开始编写触发按下PI键调出P时的动画安妮尔斯。

Now that we are done setting up our animations for our two Panels, we can begin writing code that will trigger the animations when the P and I keys are hit to bring up the Panels.

使用代码设置动画器的参数

Setting the Animator’s Parameters with code

我们有一个名为ShowHidePanels.cs的脚本附加到主摄像头,当按下PI键时,它会调出暂停面板库存面板。遗憾的是,它不再起作用,因为动画现在取代了我们在其中设置的画布组。我们可以重复使用逻辑,但需要做一些工作才能让我们的面板再次弹出。

We have a script named ShowHidePanels.cs attached to the Main Camera that would bring the Pause Panel and Inventory Panel up when the P and I keys, respectively, are pressed. Sadly, it no longer functions, since the animations now supersede the properties of the Canvas Groups we set within it. We can reuse the logic but will have to do a bit of work to get our Panels popping up again.

我们对ShowHidePanels.cs所做的更改将导致前一章场景中的面板停止显示。如果您计划访问前一章场景,请保存此脚本的第二个副本,以便以后访问。

The changes that we will make to ShowHidePanels.cs will cause the Panels in preceding chapter scenes to stop appearing. If you plan on accessing the previous chapter scenes, save a secondary copy of this script as it is now so that you can access it later.

要使用代码触发暂停面板库存面板上的动画,请完成以下步骤:

To trigger the animations on the Pause Panel and Inventory Panel with code, complete the following steps:

  1. 打开ShowHidePanels.cs脚本。注释掉Start()Update()方法中使用TogglePanel()的所有代码。注释掉这些行后,您应该看到以下内容:
    无效开始()
    {
        // 启动时初始化面板
        切换面板(库存面板,inventoryUp);
        切换面板(pausePanel,pauseUp);
    }
    无效更新()
    {
        // 处理库存面板切换
        如果(输入.GetKeyDown(KeyCode.I)&&!pauseUp)
        {
            库存上升 = !库存上升;
            切换面板(库存面板,inventoryUp);
        }
        // 处理暂停面板切换
        if (Input.GetButtonDown("暂停"))
        {
            暂停 = !暂停;
            切换面板(pausePanel,pauseUp);
            Time.timeScale = pauseUp ? 0 : 1; // 通过设置时间尺度暂停/取消暂停游戏
        }
    }
  2. Open the ShowHidePanels.cs script. Comment out all the code in the Start() and Update() methods that use TogglePanel(). After you comment out those lines, you should see the following:
    void Start()
    {
        // Initialize Panels on Start
        TogglePanel(inventoryPanel, inventoryUp);
        TogglePanel(pausePanel, pauseUp);
    }
    void Update()
    {
        // Handle inventory Panel toggle
        if (Input.GetKeyDown(KeyCode.I) && !pauseUp)
        {
            inventoryUp = !inventoryUp;
            TogglePanel(inventoryPanel, inventoryUp);
        }
        // Handle pause Panel toggle
        if (Input.GetButtonDown("Pause"))
        {
            pauseUp = !pauseUp;
            TogglePanel(pausePanel, pauseUp);
            Time.timeScale = pauseUp ? 0 : 1; // Pause/unpause game by setting time scale
        }
    }
  3. 我们现在不再使用 Canvas Group 组件来引用Pause PanelInventory Panel ,而是使用它们的 Animator 组件来引用它们。在的顶部创建以下两个变量声明:
    公共动画师inventoryPanelAnim;
    公共动画师pausePanelAnim;
  4. Instead of referencing the Pause Panel and Inventory Panel with their Canvas Group components, we’ll now reference them with their Animator components. Create the following two variable declarations at the top of the class:
    public Animator inventoryPanelAnim;
    public Animator pausePanelAnim;
  5. 现在让我们创建一个新的名为FadePanel()的方法接受Animatorbool参数:
    公共无效FadePanel(动画动画,bool show)
    {
        如果(显示)
        {
            动画.设置触发器(“淡入”);
        }
        别的
        {
            动画.设置触发(“淡出”);
        }
    }
  6. Now let’s create a new method called FadePanel() that accepts Animator and bool parameters:
    public void FadePanel(Animator anim, bool show)
    {
        if (show)
        {
            anim.SetTrigger("FadeIn");
        }
        else
        {
            anim.SetTrigger("FadeOut");
        }
    }
  7. 更新 Update ()方法,现在调用新的FadePanel()方法,而之前调用的是TogglePanel()方法。以下代码中的粗体文本表示添加的代码:
    无效更新()
    {
        // 库存面板
        如果(输入.GetKeyDown(KeyCode.I)&&!pauseUp)
        {
            库存上升 = !库存上升;
            //切换面板(inventoryPanel,inventoryUp);
            FadePanel(inventoryPanelAnimator,inventoryUp);
        }
        // 暂停面板
        if (Input.GetButtonDown("暂停"))
        {
            暂停 = !暂停;
            //切换面板(pausePanel,pauseUp);
            FadePanel(pausePanelAnimator,pauseUp);
            时间.timeScale = Convert.ToInt32(pauseUp);
        }
    }

    这就是我们对代码所要做的全部工作。

  8. Update the Update() method to now call the new FadePanel() method where the TogglePanel() method was once called. The bolded text in the following code signifies the added code:
    void Update()
    {
        // inventory Panel
        if (Input.GetKeyDown(KeyCode.I) && !pauseUp)
        {
            inventoryUp = !inventoryUp;
            //TogglePanel(inventoryPanel, inventoryUp);
            FadePanel(inventoryPanelAnimator, inventoryUp);
        }
        // pause Panel
        if (Input.GetButtonDown("Pause"))
        {
            pauseUp = !pauseUp;
            //TogglePanel(pausePanel, pauseUp);
            FadePanel(pausePanelAnimator, pauseUp);
            Time.timeScale = Convert.ToInt32(pauseUp);
        }
    }

    That’s all we have to do to the code.

  9. 库存面板暂停面板拖到显示隐藏面板组件上的相应插槽中
  10. Drag the Inventory Panel and the Pause Panel into their appropriate slots on the Show Hide Panels component:
图 14.41:显示隐藏面板组件的属性

图 14.41:显示隐藏面板组件的属性

Figure 14.41: The properties of the Show Hide Panels component

  1. 玩游戏并观察它的运行情况。库存面板应根据需要出现和消失,但暂停面板不会按预期淡出。这是因为以下代码行会停止所有依赖于按照时间尺度:
    时间.timeScale = Convert.ToInt32(pauseUp);

    这行代码用于有效暂停游戏。但是,这意味着它暂停了我们的暂停面板弹出动画。别担心,您仍然可以使用这个简单的暂停代码并在游戏暂停时运行动画。您所要做的就是告诉暂停面板上的动画器,当时间刻度设置为0时它仍然可以运行。您可以通过将动画器组件上的更新模式从正常更改为未缩放时间来实现这一点:

  2. Play the game and watch it kind of work. Inventory Panel should appear and disappear as necessary, but the Pause Panel won’t fade out the way it is supposed to. This is because the following line of code stops all animations from happening that depend on time scale:
    Time.timeScale = Convert.ToInt32(pauseUp);

    That line of code was used to effectively pause the game. However, that means it’s pausing our Pause Panel pop-up animation. Don’t worry, you can still use this simple pause code and run animations when the game is paused. All you have to do is tell the Animator on the Pause Panel that it can still function when the time scale is set to 0. You do this by changing the Update Mode on the Animator component from Normal to Unscaled Time:

图 14.42:将更新模式设置为非标度时间

图 14.42:将更新模式设置为非标度时间

Figure 14.42: Setting Update Mode to Unscaled Time

  1. 库存面板的动画组件上将更新模式设置为非缩放时间
  2. Set the Update Mode to Unscaled Time on the Inventory Panel’s Animator component as well.

玩游戏,你应该流畅地动画暂停和库存面板。现在我们可以继续进行更复杂的即时通讯。

Play the game, and you should have smoothly animating Pause and Inventory Panels. We can now move on to a more complex animation.

制作复杂的战利品盒动画

Animating a complex loot box

在这个例子中,我们将使用一个新场景。我们将创建的动画有点复杂——一个箱子会飞进场景,然后等待玩家打开它。一旦玩家打开它,宝箱就会动画打开,粒子系统会弹出。然后,三个收藏品会按顺序飞出。每个收藏品都会有自己的闪亮动画开始播放。

For this example, we’ll work with a new scene. The animation we will create is a bit complicated—a chest will fly into the scene and then wait for the player to open it. Once the player opens it, the chest will animate open with a particle system that pops in front of it. Then, three collectibles will fly out in sequence. Each collectible will have its own shiny animation that begins to play.

下图是一个故事板,展示了动画播放的几个关键帧:

The following figure is a storyboard of sorts that shows a few keyframes of the animation playing out:

图 14.43:此示例动画的最终版本

图 14.43:此示例动画的最终版本

Figure 14.43: The final version of the animation from this example

在本章中,我们将介绍除粒子系统之外的所有内容,粒子系统将在下一章中介绍。

In this chapter, we will cover all items except for the Particle System, which we will cover in the next chapter.

笔记

Note

箱子精灵表取自https://bayat.itch.io/platform-game-assets,物品精灵表取自https://opengameart.org/content/shining-coin-shining-health-shining-power-up-sprite-sheets

The chest sprite sheet was obtained from https://bayat.itch.io/platform-game-assets and the item sprite sheets were obtained from https://opengameart.org/content/shining-coin-shining-health-shining-power-up-sprite-sheets.

这个例子有很多内容使用它。构建起来并不特别复杂,但提供从头开始构建它的步骤需要太多步骤。这将超出本书的范围;但此时,希望您可以查看已经构建好的场景并了解它是如何实现的。因此,我们将从一个包文件开始这个示例,该文件包含放置在场景中的所有项目、一些已创建的动画以及包含的所有新精灵表。

This example has a lot going on with it. It is not particularly complicated to build out, but providing the steps to build it entirely from scratch would require too many steps. That would go beyond the scope of this book; but at this point in the book, you can hopefully look at a scene that’s already been built out and understand how it was achieved. Therefore, we will start this example with a package file that has all the items placed in the scene, some of the animations already created, and all the new sprite sheets included.

开始之前,导入第 14 章- 示例 - LootBox - Start.unitypackage 资产包。

Before you begin, import the Chapter 14 - Examples - LootBox - Start.unitypackage asset package.

如果您想查看完整的示例,请查看标有第 14 章- 示例 - LootBox - End.unitypackage的包。

If you’d like to view the completed example, view the package labeled Chapter 14 - Examples - LootBox - End.unitypackage.

笔记

Note

Unity Layers 不会保存在 Unity 资源包中。示例将介绍如何创建名为UI Particles 的Layer并让摄像机忽略或包含该 Layer,但这不会显示在提供的包中。

Unity Layers do not save in Unity asset packages. The example will describe creating a Layer named UI Particles and having the cameras ignore or include the Layer, but this is not displayed in the provided package.

为了举例为了更容易理解,我将步骤分为三个不同的部分。为了完成此示例,我们将执行以下功能:

To make the example easier to absorb, I have broken the steps into three distinct sections. To complete this example, we will perform the following functions:

  1. 为各个项目设置各种动画剪辑
  2. Set up the various animation clips for the individual items.
  3. 将所有动画结合在一起,并确保使用状态机(又名 Animator)适当地计时。
  4. Tie all the animations together and make sure that they are timed appropriately using a state machine, aka Animator.
  5. 创建箱子打开时显示的粒子系统,并确保它在 UI 中正确显示(将在下文中完成)章)。
  6. Create the Particle System that displays when the chest opens and make sure that it displays properly within the UI (which will be completed in the following chapter).

设置动画

Setting up the animations

让我们从为场景中的每个对象创建动画开始本节。要创建所有为此场景的动画,请完成以下步骤:

Let’s start this section by creating the animations for each of the objects within the scene. To create all the animations for this scene, complete the following steps:

  1. 如果你还没有这样做,请导入第 14 章- 示例 - LootBox - Start.unitypackage。你应该看到一个有两个 Canvas 的场景——一个带有按钮和背景图像,另一个带有箱子、物品和按钮,如下面的屏幕截图所示:
    图 14.44:包的场景布局

    图 14.44:包的场景布局

    导入包后,您还会注意到Assets文件夹中提供了一些动画剪辑和控制器

  2. If you have not done so already, import Chapter 14 - Examples - LootBox - Start.unitypackage. You should see a scene that has two Canvases—one with a button and a background image and another with a chest, items, and a button, as shown in the following screenshot:

    Figure 14.44: The scene layout of the package

    After importing the package, you will also note that there are a few animation clips and controllers provided in the Assets folder.

  1. Chest对象需要两个动画,一个是从屏幕侧面飞进来的,另一个是精灵表打开的。我们先制作飞进来的动画。在层次结构中选择Chest对象,然后在动画窗口中选择Create以创建一个新的动画剪辑。将新的动画剪辑命名为ChestFlyingIn.anim并将其保存在Assets/Animations/LootBox/Clips文件夹中。
  2. The Chest object needs two animations, one of it flying in from the side of the screen and one of its sprite sheet opening. Let’s make the flying in animation first. Select the Chest object in the Hierarchy and then select Create in the Animation Window to create a new Animation Clip. Name the new Animation Clip ChestFlyingIn.anim and save it in the Assets/Animations/LootBox/Clips folder.
  3. 在Assets/Animations/LootBox/Clips文件夹中自动创建了一个名为Chest.controller的新动画控制器。将其移动到Assets/Animations/LootBox/Controllers文件夹。
  4. A new Animation Controller named Chest.controller was automatically created in the Assets/Animations/LootBox/Clips folder. Move it to the Assets/Animations/LootBox/Controllers folder.
  5. 在动画窗口中,选择添加属性并展开Rect Transform 。单击Anchored Position旁边的加号。这将允许我们在画布内为箱子的位置设置动画:
  6. Within the Animation window, select Add Property and expand Rect Transform. Click on the plus sign next to Anchored Position. This will allow us to animate the position of the Chest within the Canvas:
图 14.45: ChestFlyingIn 动画

图 14.45: ChestFlyingIn 动画

Figure 14.45: The ChestFlyingIn animation

  1. 目前,动画正在一秒长,比我们想要的要长一点。将第二个关键帧移动到0:30标记处,这样它的长度将为半秒。
  2. Currently, the animation is one second long, which is a bit longer than we want. Move the second keyframe to the 0:30 mark so that it will be half a second long.
  3. 我们将在场景中的各个关键帧处移动物体,并希望将它们保存在动画中。因此,要让动画记录您在场景中所做的任何更改,请选择动画窗口中的记录按钮。
  4. We will be moving objects in the scene at various keyframes and will want them to be saved in the animation. So, to have the animation record any of the changes you make in the scene, select the record button in the Animation window.
  5. 宝箱在场景中的当前位置就是我们希望它在飞入动画结束时所在的位置,因此我们不会影响第二个关键帧的位置。但是,我们希望它从屏幕外开始,因此将动画播放头放在第一帧上,使用移动工具将宝箱移出画布区域。由于选择了记录,这将更新宝箱在第一帧的位置。这由Rect Transform上的红色色调表示。
    图 14.46:移动箱子并记录属性

    图 14.46:移动箱子并记录属性

    如果你拖动播放头,你会看到胸部从左向右移动。

  6. The position of the chest in the scene right now is where we want it to be at the end of the fly-in animation, so we will not affect the position at the second keyframe. However, we want it to start off screen, so with the animation playhead on the first frame, use the Move tool to move the chest outside of the Canvas area. Since record is selected, this will update the Chest’s position at the first frame. This is indicated by the red tint on the Rect Transform.

    Figure 14.46: Moving the chest and recording the properties

    If you scrub the playhead, you will see the chest move from left to right.

  1. 现在,让我们制作胸部以弧线而不是直线飞行。我们将使用动画曲线来实现这一点。选择“曲线”选项卡,如下所示:
    图 14.47:选择曲线菜单

    图 14.47:选择曲线菜单

    绿线代表y属性锚定位置。选择Anchored Postion.y属性以将聚焦。

  2. Now, let’s make the chest fly in at an arc instead of a straight line. We’ll do this with Animation Curves. Select the Curves tab, as follows:

    Figure 14.47: Selecting the Curves menu

    The green line represents the y property of the anchored position. Select the Anchored Postion.y property to focus on it.

  1. 选择第一和第二个关键帧锚点来影响它们的手柄。移动它们的手柄直到绿色曲线看起来更像一个圆弧:
    图 14.48:调整曲线的锚点

    图 14.48:调整曲线的锚点

    现在,当你播放动画时,你会看到胸部沿着弧线路径移动。

  2. Select the first and second key frame anchors to affect their handles. Move their handles until the green curve looks more like an arc:

    Figure 14.48: Adjusting the anchors of the curve

    Now, when you play the animation, you will see the chest move in an arcing path.

  1. 让我们让宝箱在飞行时逐渐消失通过为动画添加颜色属性。返回时间轴的Dopesheet视图。选择Add Property,然后展开Image 。单击Color旁边的加号。在第一帧上,将Color.a属性更改为0,使胸部在第一帧上不可见:
  2. Let’s have the chest fade in as it flies by adding a color property to the animation. Return to the Dopesheet view of the timeline. Select Add Property, then expand Image. Click on the plus sign next to Color. On the first frame, change the Color.a property to 0 to make the chest invisible on the first frame:
图 14.49:ChestFlyingIn 动画第一帧的属性

图 14.49:ChestFlyingIn 动画第一帧的属性

Figure 14.49: The properties of the first frame of the ChestFlyingIn animation

  1. 每当创建新动画时,都会自动将其设置为循环播放。我们不希望此动画在循环,因此从Project视图中选择ChestFlyingIn动画剪辑以查看其Inspector属性。取消选择循环时间复选框。
  2. Whenever a new animation is created, it is automatically set to loop. We don’t want this animation to play on a loop, so select the ChestFlyingIn Animation Clip from the Project view to see its Inspector properties. Deselect the Loop Time checkbox.
  3. 我们不希望ChestFlyingIn动画剪辑在场景开始时自动播放。由于这是为Chest创建的第一个动画剪辑,因此它将被设置为 Animator 默认状态。选择Chest后,打开Animator 窗口。
  4. We don’t want the ChestFlyingIn Animation Clip to automatically play when the scene starts. Since this was the first Animation Clip created for the Chest, it will be set to the Animator default state. With the Chest selected, open the Animator Window.
  5. 在动画器窗口中单击右键并选择“创建状态” | “空” ,创建一个新的默认状态。将新状态重命名为“空状态” ,然后右键单击并选择“设置为图层默认状态”,将其设置为默认状态。稍后我们将对 Chest 的动画器进行更多操作,但现在,这就是我们要做的全部
  6. Create a new default state by right-clicking within the Animator Window and selecting Create State | Empty. Rename the new state Empty State and set it as the default state by right-clicking on it and selecting Set as Layer Default State. We will do more with the Chest’s Animator later, but for now, this is all we are going to do.
图 14.50:胸部动画师

图 14.50:胸部动画师

Figure 14.50: The Animator of the Chest

  1. 现在,让我们设置打开宝箱的动画。在仍选择宝箱的情况下,在动画窗口中,从动画剪辑下拉列表中选择创建新剪辑…
    图 14.51:创建新剪辑

    图 14.51:创建新剪辑

    将新的动画剪辑命名为ChestOpening.anim,并将其保存在Assets/Animations/LootBox/Clips文件夹中。

  2. Now, let’s set up the chest opening animation. With Chest still selected, in the Animation window, select Create New Clip… from the Animation Clip dropdown list:

    Figure 14.51: Creating a new clip

    Name the new Animation Clip ChestOpening.anim and save it in the Assets/Animations/LootBox/Clips folder.

  1. 此动画剪辑将包含精灵表中的所有精灵。从项目视图中,将所有子精灵拖放到动画时间轴中。将自动添加Image.Sprite的新属性,并且所有子精灵将按顺序添加到时间轴中:
  2. This Animation Clip will contain all the sprites from the sprite sheet. From the Project view, drag and drop all the sub-sprites into the Animation timeline. A new property for Image.Sprite will automatically be added, and all the sub-sprites will be added to the timeline in a sequence:
图 14.52:胸部精灵表动画

图 14.52:胸部精灵表动画

Figure 14.52: The Chest sprite sheet animation

  1. 现在,动画太快了。它以每秒 60 帧的速度运行,但只有 6 帧。我们需要更改帧速率。首先,我们必须启用“采样率”动画窗口中的选项。选择时间线上的三个点,然后选择显示采样率
  2. Right now, the animation is way too fast. It is running at 60 frames per second, and there are only 6 frames. We need to change the frame rate. First, we must enable the Sample Rate option in the Animation Window. Select the three dots on the timeline and then select Show Sample Rate.
图 14.53:启用采样率菜单项

图 14.53:启用采样率菜单项

Figure 14.53: Enabling the Sample Rate menu item

  1. 现在Samples选项在窗口中可见,将动画的Samples值更改为12;这会将动画的帧速率更改为12 fps:
  2. Now that the Samples option is visible in the window, change the animation’s Samples value to 12; this will change the animation’s frame rate to 12 fps:
图 14.54:更改采样率

图 14.54:更改采样率

Figure 14.54: Changing the Sample Rate

  1. 由于Chest有一个动画会影响其 alpha 值,因此让我们确保此动画在播放时具有完整的 alpha。选择Add Property,然后展开Image 。单击Color旁边的加号。确保Color.a属性在第一帧和最后一帧上均为1。¶从技术上讲,我们只需要在第一帧将 alpha 设置为1,但我喜欢在这里添加开始和结束帧,这样我就可以非常确定动画用这个值做了什么。
  2. Since Chest has an animation that will affect its alpha value, let’s ensure that this animation has full alpha whenever it plays. Select Add Property, then expand Image. Click on the plus sign next to Color. Ensure that the Color.a property is 1 on both the first and last frames.¶Technically, we only need the alpha set to 1 on the first frame, but I like to add a start and end frame here so that I am very sure about what the animation is doing with that value.
  3. 我们不想让这个动画循环播放,所以从Project视图中选择ChestOpening动画剪辑来查看其Inspector属性。取消选择Loop Time复选框。
  4. We don’t want this animation to play on a loop, so select the ChestOpening Animation Clip from the Project view to take a look at its Inspector properties. Deselect the Loop Time checkbox.
  5. 本示例所需的所有其他动画都已设置完毕。它们在设置上与此动画或上例中创建的动画非常相似。但是,它们尚未连接到正确的 GameObject。

    让我们为Chest Open Canvas中的其他对象添加动画。将Coin Animator 拖到Coin GameObject的检查器中,将Heart Animator 拖到Heart GameObject的检查器中,将PowerUp Animator 拖到PowerUp GameObject 的检查器中。这些动画器中的每一个都可以在Assets/Animations/Loot Box/Controllers文件夹中找到。现在,您可以通过选择 GameObjects 并在动画窗口中按下播放来预览所有物品从箱子中弹出并闪闪发光的动画(在游戏视图中播放尚不会显示正在播放的动画)。

  6. All of the other animations needed for this example have already been set up. They are very similar to this one in setup or the ones created in the preceding example. However, they are not hooked up to the correct GameObjects.

    Let’s give the other objects in Chest Open Canvas their animations. Drag the Coin Animator to the Inspector of the Coin GameObject, the Heart Animator to the Inspector of the Heart GameObject, and the PowerUp Animator to the PowerUp GameObject’s Inspector. Each of these Animators can be found in the Assets/Animations/Loot Box/Controllers folder. You can now preview the animations of all the items popping out of the chests and shining by selecting the GameObjects and pressing play in the Animation window (Play in the Game view will not yet show the animations playing).

  7. 让我们将Chest和所有对象初始化为不可见。从层次结构中选择ChestCoinHeartPowerUp游戏对象。在其 Image 组件中,将其Color的 alpha 值更改 0
  8. Let’s initialize the Chest and all of the objects as invisible. Select the Chest, Coin, Heart, and PowerUp GameObjects from the Hierarchy. In their Image component, change the alpha values of their Color to 0.
  9. Chest Open Canvas和该 Canvas 上的Button也都具有动画。这些动画将影响对象上的 Canvas Group 组件。不过,目前它们还没有 Canvas Group 组件。因此,向Chest Open Canvas及其Button添加一个 Canvas Group 组件。
  10. The Chest Open Canvas and the Button on that Canvas both have animations as well. These animations will affect a Canvas Group component on the objects. Right now, they don’t have Canvas Group components, though. So, add a Canvas Group component to Chest Open Canvas and its child Button.
  11. 初始化两个新的 Canvas Group 组件,使其Alpha值为0,并将其InteractableBlocks Raycasts属性设置false

    现在您应该只会在场景中看到“开始”按钮。

  12. Initialize the two new Canvas Group components to have Alpha values of 0 and their Interactable and Blocks Raycasts properties set to false.

    You should now only see the Start Button in the scene.

  13. 现在,将CanvasGroupFadeInOut Animator 添加到Chest Open Canvas及其Button
  14. Now, add the CanvasGroupFadeInOut Animator to Chest Open Canvas and its child Button.

动画现在完全为每个对象设置动画。我们仍然需要完成胸部动画控制器的工作并为各种动画添加更多逻辑,但我们已经完成了动画剪辑现在

The animations are now completely set up for each of the objects. We still need to finish working on the Animator Controller for the Chest and add some more logic for the various Animators, but we are done with the Animation Clips for now.

构建状态机并设定动画时间

Building a State Machine and timing the animations

接下来要做的是设置我们的状态机并编写使各种动画播放的代码

The next thing to do is set up our state machine and write the code that will make the various animations play.

要连接各种动画并让它们在正确的时间播放,请完成以下步骤:

To hook up the various animations and have them play at the correct time, complete the following steps:

  1. 我们首先创建状态机,作为动画序列的逻辑。在Assets/Animations/Loot Box/Controllers中创建一个名为ChestOpeningStateMachine.controller的新动画控制器
  2. We’ll start by creating the state machine that will work as the logic for our animation sequences. Create a new Animator Controller named ChestOpeningStateMachine.controller in Assets/Animations/Loot Box/Controllers.
  3. 打开ChestOpeningStateMachine动画控制器并创建 12 个新的空状态。排列、命名和转换状态,如以下屏幕截图所示:
    图 14.55:动画的状态机

    图 14.55:动画的状态机

    状态机如图所示上面的截图演示了打开宝箱的动画和交互的事件顺序。当游戏等待玩家按下按钮继续播放动画时,将播放标记为“等待玩家”的状态。其他动画将根据定时事件自动播放。

  4. Open the ChestOpeningStateMachine Animator Controller and create 12 new Empty States. Arrange, name, and transition the States, as shown in the following screenshot:

    Figure 14.55: The State Machine for the Animation

    The state machine shown in the preceding screenshot demonstrates the sequence of events for the animations and interactions of the chest opening. The states labeled Waiting On Player will play when the game is waiting for the player to press a button to proceed with the animation. The other animations will automatically play based on timed events.

  1. 前面步骤中创建的状态机实际上不包含任何动画。它只是一个流程图,描述游戏中当前发生的事情。我们还会用它来向场景中的各个物体发送信息,让它们知道它们应该做什么或不应该做什么。由于我们只想让它控制这个序列的逻辑,而不是实际为场景中的任何内容设置动画,我们可以将它添加到 Animator 组件上,并将其添加到场景中的任何对象上。因此,让我们将它添加到Main Camera。将ChestOpeningStateMachine Animator 从项目视图拖到Main Camera的 Inspector 中。
  2. The state machine created in the previous steps will not actually contain any animations. It is simply a flow chart describing what is currently happening in the game. We will also use it to send information to the various objects in the scene to let them know what they should or should not be doing. Since we just want this to control the logic of this sequence, and not actually animate anything in the scene, we can add it on an Animator component to any object in the scene. Therefore, let’s add it to the Main Camera. Drag the ChestOpeningStateMachine Animator from the project view to the Inspector of the Main Camera.
  3. 现在,我们需要设置状态机中各个状态的转换条件。创建四个动画触发参数,分别命名ShowChestOpenChestCloseChestAnimationComplete
  4. Now, we will need to set the conditions of transition for the various states within the state machine. Create four animation Trigger Parameters, named ShowChest, OpenChest, CloseChest, and AnimationComplete.
  5. 现在,为每个转换设置触发条件,如下面的屏幕截图所示;对于每个转换,请确保取消选择“具有退出时间固定持续时间”
  6. Now, set Trigger Conditions for each transition, as shown in the following screenshot; with each transition, make sure that you deselect Has Exit Time and Fixed Duration:
图 14.56:各种转换的触发器

图 14.56:各种转换的触发器

Figure 14.56: The triggers for the various transitions

笔记

Note

我强烈建议你将此图像添加为书签或打印屏幕截图在执行此示例时,您可以查看ChestOpeningStateMachine动画器的动画。将此作为流程图,您可以在执行此示例时轻松参考,这将使完成的步骤更容易遵循。

I highly recommend that you bookmark this image or print out a screenshot of your ChestOpeningStateMachine Animator while working through this example. Having this as a flow chart that you can easily reference while working on this example will make the steps being completed a lot easier to follow.

  1. 在编写代码之前,让我们先设置Chest的动画器。目前, Chest的动画器应该看起来如下:
  2. Before we write code, let’s set up the Animator for the Chest. Currently, the Animator of the Chest should look something as follows:
图 14.57:胸部动画器

图 14.57:胸部动画器

Figure 14.57: The Chest Animator

仅限动画师包含动画状态,但尚未表明它们是如何连接的。我们需要创建过渡并设置动画参数。重新排列状态并将过渡添加到 Animator,使其显示如下:

The Animator only contains the animation states but does not yet indicate how they are all connected. We will need to create transitions and set up the Animation Parameters. Rearrange the states and add transitions to the Animator so it appears as follows:

图 14.58:胸部动画器已更新

图 14.58:胸部动画器已更新

Figure 14.58: The Chest Animator updated

  1. 创建三个动画触发参数,分别命名ShowChestOpenChestReset
  2. Create three animation Trigger Parameters, named ShowChest, OpenChest, and Reset.
  3. 现在,设置触发条件每次转换,如下面的屏幕截图所示;每次过渡,请确保取消选择“具有退出时间固定持续时间”
  4. Now, set Trigger Conditions for each transition, as shown in the following screenshot; with each transition, make sure that you deselect Has Exit Time and Fixed Duration:
图 14.59:胸部动画器的触发器

图 14.59:胸部动画器的触发器

Figure 14.59: The Triggers of the Chest Animator

  1. 现在我们的动画器都已正确设置,我们可以开始编码了。当玩家在指定状态下单击指定按钮时,我们将使用ChestOpeningStateMachine动画器播放每个适当的动画。然后, ChestOpeningStateMachine动画器将自动在完整动画序列中出现的各种对象的动画器上设置适当的触发器。我们需要一种方法来跟踪场景中哪些项目具有将由ChestOpeningStateMachine动画器控制的动画器、它们的各种参数是什么,以及设置这些参数需要满足哪些条件。我们将在一个脚本中跟踪所有这些信息。创建一个名为ChestAnimControls的新脚本并将其保存Assets/Scripts/LootBox中。
  2. Now that our Animators are all appropriately set, we can begin coding. We will use the ChestOpeningStateMachine Animator to make each appropriate animation play when the player clicks on the specified button during the specified state. The ChestOpeningStateMachine Animator will then automatically set the appropriate triggers on the Animators on the various objects that appear in the full animated sequence. We need a way to keep track of which items in the scene have Animators that will be controlled by the ChestOpeningStateMachine Animator, what their various parameters are, and what conditions need to be met to have those parameters set. We’ll keep track of all of this information in a single script. Create a new script called ChestAnimControls and save it in Assets/Scripts/LootBox.
  3. 为了给我们提供一种简洁的方式来跟踪所有必要的信息,我们将使用类和一个枚举列表(我会解释什么这些是什么以及我们为什么暂时使用它们)。从ChestAnimControls类中删除Start()Update()函数,并编写以下代码:
    // 不同类型的参数
    公共枚举参数类型
    {
        浮点参数,
        int参数,
        boolParam,
        触发参数
    }
    // 动画参数的属性
    [系统.可序列化]
    公共类参数属性
    {
        public string paramterString; // 什么字符串设置它?
        public string whichState; // 调用它的状态的名称,null=未被状态机调用
        public TypesOfParameters paramType; // 它是什么类型的 Animator Parameter?
        public float floatValue; // 需要浮点值,如果是浮点数
        public int intValue; // 需要 int 值,如果是 int
        public bool boolValue; // 需要 Bool 值,如果是 bool
    }
    // 列出所有可动画的对象及其参数
    [系统.可序列化]
    公共类 AnimatorProperties
    {
        public string name; // 因此名称将出现在检查器中,而不是“元素 0、元素 1 等”
        public Animator theAnimator; // 动画师
        public List<ParameterProperties> animatorParameters; // 其参数属性
    }

    您会注意到,上述代码包含以下三个部分:枚举的TypesOfParametersParameterProperties类和AnimatorProperties类。首先,让我们看一下枚举的TypesOfParameters。这是可在 Animator 中使用的 Animator 参数类型的列表。枚举列表是一种自定义类型,其中包含一组用名称表示的常量。使用枚举列表的好处是列表在 Inspector 中显示为下拉菜单。现在,让我们看一下ParameterProperties类。场景中动画的每个对象都有一组与其 Animator 参数相关的属性,我们需要跟踪这些属性。类可以成为将数据集分组在一起的有效工具。因此,我使用类来对参数的名称、将设置参数的状态、参数的类型以及其值(如果是浮点整数布尔参数)进行分组。请注意,参数类型是使用枚举列表TypesOfParameters定义的。这样做是因为每个动画器参数都有一组有限且特定的参数。现在,让我们看看AnimatorProperties子类。对于场景中的每个对象,我们需要跟踪其名称、动画器及其所有参数以及设置它们的条件。请注意,参数列表及其属性由ParameterProperties类定义。使用 Unity 的一大好处是能够分配和在检查器中查看公共变量。但是,当您在类中创建类时,除非您将[System.Serializable]置于类上方,否则公共变量在检查器中不可见。这为子类提供了Serializable属性,并允许其公共变量在检查器中可见。

  4. To give us a nice, clean way of keeping track of all of the necessary information, we’ll use classes and an enumerated list (I’ll explain what these are and why we are using them momentarily). Delete the Start() and Update() functions from the ChestAnimControls class, and write the following code:
    // The different types of parameters
    public enum TypesOfParameters
    {
        floatParam,
        intParam,
        boolParam,
        triggerParam
    }
    // Properties of animation parameters
    [System.Serializable]
    public class ParameterProperties
    {
        public string parameterString; // What string sets it?
        public string whichState; // Name of the state it's called from, null=not called by the state machine
        public TypesOfParameters parameterType; // What type of Animator Parameter is it?
        public float floatValue; // Float value required, if float
        public int intValue; // Int value required, if int
        public bool boolValue; // Bool value required, if bool
    }
    // Make a list of all animatable objects and their parameters
    [System.Serializable]
    public class AnimatorProperties
    {
        public string name; // So the name will appear in the inspector rather than "Element 0, Element 1, etc"
        public Animator theAnimator; // The animator
        public List<ParameterProperties> animatorParameters; // Its parameter properties
    }

    You’ll note that the preceding code has the following three parts: the TypesOfParameters enumerated, the ParameterProperties class, and the AnimatorProperties class. First, let’s look at the TypesOfParameters enumerated. This is a list of the types of Animator Parameters that can be used within an Animator. An enumerated list is a custom type that contains a set of constants that are represented with names. A benefit of using an enumerated list is that the list appears as a dropdown menu within the Inspector. Now, let’s look at the ParameterProperties class. Each object that is animated within the scene has a set of properties related to its Animator Parameters that we need to keep track of. A class can be an effective tool for grouping sets of data together. Therefore, I used a class to group the name of the parameter, which state the parameter will be set in, what type of parameter it is, and its value if it is a float, integer, or Boolean parameter. Note that the type of parameter is defined using the enumerated list, TypesOfParameters. This was done because there is a finite and specific set of parameters available for each animator parameter. Now, let’s look at the AnimatorProperties subclass. For each object in the scene, we will need to keep track of its name, its animator, and all of its parameters along with the conditions in which they are set. Note that the list of parameters and their properties is defined by the ParameterProperties class. A big benefit of working with Unity is the ability to assign and view public variables in the Inspector. However, when you create a class within a class, the public variables are not visible within the Inspector unless you place [System.Serializable] above the class. This gives the subclass the Serializable attribute and allows its public variables to be visible in the Inspector.

笔记

Note

我想指出的是,此代码允许动画器具有FloatIntBool参数,尽管我们在任何动画器中使用的唯一参数是触发器。我以这种方式编写它是为了使其更通用,以便您将来可以将此代码重复用于其他动画

I would like to point out that this code allows for the Animators to have Float, Int, and Bool parameters, even though the only parameters we use in any of our Animators are Triggers. I wrote it in this way to make it work more universally so that you can reuse this code for other animations in the future.

  1. 如果您发现我们在上一步中编写的代码令人不知所措,请不要担心 - 在 Inspector 中看到所有内容会让您更加清楚。到目前为止,我们所做的就是设置几个不同的数据组。现在,我们需要创建一个使用该信息的变量。我们需要所有动画项目的列表,因此请将以下代码添加到您的脚本中:
    public List<AnimatorProperties> animatedItems; //此状态机控制的所有动画项目
  2. If you find the code we wrote in the preceding step overwhelming, don’t worry—seeing it all listed out in the Inspector will clear it up a bit. All we have done so far is set up a few different groups of data. Now, we will need to create a variable that will use the information. We need a list of all animated items, so add the following code to your script:
    public List<AnimatorProperties> animatedItems; //all the animated items controlled by this state machine
  3. 将ChestAnimControls脚本拖入主摄像头的检查器 (Inspector),并将其附加到主摄像头
  4. Attach the ChestAnimControls script to the Main Camera by dragging it into its Inspector.
  5. 在主摄像机的检查器中,单击动画项目旁边的箭头以展开列表:
  6. In the Inspector of the Main Camera, click on the arrow next to Animated Items to expand the list:
图 14.60:胸部动画控制组件

图 14.60:胸部动画控制组件

Figure 14.60: The Chest Anim Controls component

  1. 我们总共有六件商品需要让动画师受此脚本和国家控制我们创建的机器。因此,将列表的大小更改为6。展开动画器项目|元素 0及其动画器参数
    图 14.61:胸部动画控件的动画器属性

    图 14.61:胸部动画控件的动画器属性

    请记住animatedItems变量是AnimatorProperties的列表。因此,元素 0(以及所有其他元素)包含AnimatorProperties类中分组的所有项目

  2. We have a total of six items that need to have their Animators controlled by this script and the State Machine we created. So, change the List’s size to 6. Expand Animator Items | Element 0 and its Animator Parameters:

    Figure 14.61: The AnimatorProperties of the Chest Anim Controls

    Remember that the animatedItems variable was a list of AnimatorProperties. So, Element 0 (and all the other Elements for that matter) contains all the items that were grouped in the AnimatorProperties class.

  1. 我们需要列出的第一项数据是Chest Open Canvas游戏对象。在名称槽中输入Chest Open Canvas,然后将Chest Open Canvas从层次结构拖到动画器槽中。¶ 在名称槽中输入Chest Open Canvas 后,您将看到元素 0标签是替换为Chest Open Canvas。每当您在 Unity 的 Inspector 中有一个对象列表时,如果对象中的第一个项目是字符串,则该字符串将替换Element x标签:
  2. The first item we will need to list data for is the Chest Open Canvas GameObject. Type Chest Open Canvas in the Name slot and drag the Chest Open Canvas from the Hierarchy into the The Animator slot.¶Once you type Chest Open Canvas in the Name slot, you’ll see that the Element 0 label is replaced with Chest Open Canvas. Whenever you have a list of objects in Unity’s Inspector, if the first item in the object is a string, the string will replace the Element x label:
图 14.62:胸部打开画布属性

图 14.62:胸部打开画布属性

Figure 14.62: The Chest Open Canvas properties

  1. 现在,我们需要列出Chest Open Canvas的 Animator 中使用的所有参数以及设置它的条件。它有两个参数需要列出数据,因此将Animator Parameters的大小更改为2。展开两个生成的Elements如下所示:
    图 14.63:ParameterProperties 和 TypesOfParameters

    图 14.63:ParameterProperties 和 TypesOfParameters

    请记住,animatorParameters变量ParameterProperties的列表。因此,这两个元素包含ParameterProperties类中分组的所有项目。此外,在ParameterProperties类中,parameterType是一个TypesOfParameters变量。TypesOfParameters是一个枚举列表,因此该类型的任何变量都将作为下拉列表出现菜单中包含定义列表中出现的选项

  2. Now, we will need to list out all the parameters that are used within Chest Open Canvas’s Animator and the conditions under which it is set. It has two parameters that we need to list data for, so change the size of Animator Parameters to 2. Expand the two resulting Elements, as follows:

    Figure 14.63: The ParameterProperties and TypesOfParameters

    Remember that the animatorParameters variable was a list of ParameterProperties. So, the two Elements contain all the items that were grouped in the ParameterProperties class. Additionally, within the ParameterProperties class, parameterType was a TypesOfParameters variable. TypesOfParameters was an enumerated list, so any variable of that type will appear as a dropdown menu with the options that appeared within the defined list.

  1. 现在我们需要列出Chest Opening Canvas的每个参数,ChestOpeningStateMachine中的哪个状态将导致设置该参数,并指定其参数类型
    图 14.64:胸部打开画布属性

    图 14.64:胸部打开画布属性

    由于每个都是触发器动画参数,我们不必担心Float ValueInt ValueBool Value的值。

  2. We now need to list out each Parameter of the Chest Open Canvas, Which State in the ChestOpeningStateMachine will cause the Parameter to be set, and specify its Parameter Type:

    Figure 14.64: The Chest Open Canvas properties

    Since each is a Trigger Animator Parameter, we do not have to worry about the values for Float Value, Int Value, or Bool Value.

  1. 现在,我们可以填写其余五个动画对象的动画师信息与我们填写Chest Open Canvas信息的方式相同。Chest 的动画师信息填写如下:
  2. Now, we can fill in the Animator information for the other five animated objects in the same way we filled out the information for the Chest Open Canvas. Fill in the information for the animator of Chest as follows:
图 14.65:箱子属性

图 14.65:箱子属性

Figure 14.65: The Chest properties

  1. 填写信息对于动画师来说Coin如下
  2. Fill in the information for the Animator of Coin as follows:
图 14.66:硬币属性

图 14.66:硬币属性

Figure 14.66: The Coin properties

  1. 填写信息对于动画师来说心脏如下
  2. Fill in the information for the Animator of Heart as follows:
图 14.67:心脏属性

图 14.67:心脏属性

Figure 14.67: The Heart properties

  1. 填写信息对于动画师来说PowerUp如下
  2. Fill in the information for the Animator of PowerUp as follows:
图 14.68:PowerUp 属性

图 14.68:PowerUp 属性

Figure 14.68: The PowerUp properties

  1. 填写信息对于动画师来说按钮如下
  2. Fill in the information for the Animator of Button as follows:
图 14.69:按钮属性

图 14.69:按钮属性

Figure 14.69: The Button properties

  1. 现在我们已经初始化并定义了状态机的所有适当数据值,让我们真正让状态机执行其适当的逻辑。首先,我们需要为 Animator 创建变量。添加以下变量初始化脚本:
    Animator theStateMachine; //状态机动画组件
  2. Now that we have all the appropriate data values for the State Machine initialized and defined, let’s actually have the State Machine perform its appropriate logic. First, we will need to create a variable for the Animator. Add the following variable initialization to your script:
    Animator theStateMachine; //the state machine animator component
  3. 现在,在Awake()函数中初始化状态机的动画器
    无效唤醒()
    {
        theStateMachine = GetComponent<Animator>(); // 获取状态机
    }
  4. Now, initialize the State Machine’s Animator in an Awake() function:
    void Awake()
    {
        theStateMachine = GetComponent<Animator>(); // Get the state machine
    }
  5. 要让状态机自动将各个动画器的各种参数设置为适当的状态,我们需要循环遍历列出的所有动画项及其列出的每个参数。如果动画项有一个参数,该参数要在状态机的当前状态下设置,我们将根据列出的条件进行设置。创建以下函数来执行该功能:
    // 功能:检查是否有动画需要设置其参数
    // 从进入状态调用
    公共无效检查参数集()
    {
        // 循环遍历所有对象
        foreach(animatorProperties 在 animatedItems 中的 animatorProp)
        {
            // 循环遍历其参数集
            foreach(animatorProp.animatorParameters 中的 ParameterProperties 参数)
            {
                // 查找当前状态下调用的
                如果(theStateMachine.GetCurrentAnimatorStateInfo(0)。IsName(参数。哪个状态))
                {
                    // 判断参数类型
                    // 浮点类型
                    如果(参数.parameterType == TypesOfParameters.floatParam)
                    {
                        animatorProp.theAnimator.SetFloat(参数.parameterString,参数.floatValue);
                    }
                    // 整数类型
                    否则,如果(参数.parameterType == TypesOfParameters.intParam)
                    {
                        animatorProp.theAnimator.SetInteger(参数.parameterString,参数.intValue);
                    }
                    // 布尔类型
                    否则,如果(参数.parameterType == TypesOfParameters.boolParam)
                    {
                        animatorProp.theAnimator.SetBool(参数.parameterString,参数.boolValue);
                    }
                    // 触发器类型
                    别的
                    {
                        animatorProp.theAnimator.SetTrigger(参数.parameterString);
                    }
                }
            }
        }
    }

    CheckForParameterSet函数将确定指定的 Animator 是否需要在状态机的当前状态下设置参数。但是,此函数不目前在任何地方调用。我们希望这个函数在任何时候被调用状态机中的状态开始。我们可以使用状态机行为来实现这一点。打开ChestOpeningStateMachine动画器并选择Canvas Fading In状态。在状态的检查器中,单击添加行为按钮。选择新脚本,输入ChestStateMachineBehaviour,然后单击创建并添加按钮。名为ChestStateMachineBehaviour的新脚本将添加到Assets文件夹中。将其移动到Assets/Scripts/LootBox文件夹并打开它。

  6. To have the State Machine automatically set the various parameters of the individual Animators at the appropriate state, we will need to loop through all of the animated items we have listed and each of their listed parameters. If the animated item has a parameter, which is to be set at the current state of the State Machine, we will set it based on the conditions listed. Create the following function to perform that functionality:
    // Functionality: Check if any of the animations need their parameters set
    // Called from enter state
    public void CheckForParameterSet()
    {
        // Loop through all of the objects
        foreach (AnimatorProperties animatorProp in animatedItems)
        {
            // Loop through its set of parameters
            foreach (ParameterProperties parameter in animatorProp.animatorParameters)
            {
                // Find the ones called on the current state
                if (theStateMachine.GetCurrentAnimatorStateInfo(0).IsName(parameter.whichState))
                {
                    // Determine parameter type
                    // Float types
                    if (parameter.parameterType == TypesOfParameters.floatParam)
                    {
                        animatorProp.theAnimator.SetFloat(parameter.parameterString, parameter.floatValue);
                    }
                    // Int types
                    else if (parameter.parameterType == TypesOfParameters.intParam)
                    {
                        animatorProp.theAnimator.SetInteger(parameter.parameterString, parameter.intValue);
                    }
                    // Bool type
                    else if (parameter.parameterType == TypesOfParameters.boolParam)
                    {
                        animatorProp.theAnimator.SetBool(parameter.parameterString, parameter.boolValue);
                    }
                    // Trigger type
                    else
                    {
                        animatorProp.theAnimator.SetTrigger(parameter.parameterString);
                    }
                }
            }
        }
    }

    The CheckForParameterSet function will determine whether a specified Animator needs a parameter set at the current state of the State Machine. However, this function is not currently called anywhere. We want this function to be called whenever a state in the State machine starts. We can accomplish this with a State Machine Behaviour. Open the ChestOpeningStateMachine Animator and select the Canvas Fading In state. In the state’s Inspector, click on the Add Behaviour button. Select New Script, enter ChestStateMachineBehaviour, and click on the Create and Add button. A new script named ChestStateMachineBehaviour will be added to the Assets folder. Move it to the Assets/Scripts/LootBox folder and open it.

  7. 此状态机行为中包含很多内容。我们只需要OnStateEnter函数。调整ChestStateMachineBehaviour类中的代码以包含以下内容,以便在ChestAnimControls脚本启动时调用其上的CheckForParameterSet函数:
    ChestAnim控制控制器脚本;
    公共无效唤醒()
    {
        // 获取保存状态机逻辑的脚本
        theControllerScript = FindObjectOfType<ChestAnimControls>();
    }
    // 当转换开始并且状态机开始评估此状态时,将调用 OnStateEnter
    公共覆盖void OnStateEnter(Animator动画师,AnimatorStateInfo stateInfo,int layerIndex)
    {
        检查参数集();
    }
  8. A lot of stuff is included within this State Machine Behaviour. We only need the OnStateEnter function. Adjust the code within the ChestStateMachineBehaviour class to include the following to call the CheckForParameterSet function on the ChestAnimControls script when it starts:
    ChestAnimControls theControllerScript;
    public void Awake()
    {
        // Get the script that holds the state machine logic
        theControllerScript = FindObjectOfType<ChestAnimControls>();
    }
    // OnStateEnter is called when a transition starts and the state machine starts to evaluate this state
    public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        theControllerScript.CheckForParameterSet();
    }
  9. 我们需要在每个没有显示Waiting On Player 的状态下使用此脚本。通过单击Add Behaviour按钮并在其 Inspector 中选择ChestStateMachineBehaviour ,将ChestStateMachineBehaviour添加到右侧列中的每个状态(没有显示Waiting On Player 的状态) 。当进入适当的状态时,状态机现在将适当地调用每个单独项目的动画,但我们没有任何实际控制状态机流程的东西
  10. We will need this script on every state that does not say Waiting On Player. Add ChestStateMachineBehaviour to each of the states in the right-hand column (the ones that do not say Waiting On Player) by clicking on the Add Behaviour button and selecting ChestStateMachineBehaviour in their Inspector. The State Machine will now appropriately call each of the individual items’ animations when the appropriate states are entered, but we don’t have anything that actually controls the flow of the state machine.
  11. 现在,它只会停留在等待玩家(显示宝箱)状态。我们需要编写一些逻辑来控制状态机内的流程。请记住每个状态表示正在等待玩家在进入下一个状态之前与游戏进行一些交互。因此,让我们通过制作一个脚本来开始我们的状态机逻辑,该脚本可以在玩家点击按钮时由按钮使用。让我们从按钮画布上显示Start的按钮开始。将以下函数添加到ChestAnimControls.cs脚本中:
    // 由玩家互动调用(等待玩家)
    公共无效PlayerInputTrigger(字符串触发器字符串)
    {
        状态机.设置触发器(触发器字符串);
    }

    此函数将触发由作为参数发送的字符串指定的状态机的触发参数。

  12. Right now, it is going to just stay in the Waiting on Player (Show Chest) state. We will need to write up some logic to control the flow within the State Machine. Remember that each of the states that say Waiting on Player is going to wait for the player to perform some interaction with the game before proceeding to the next state. So, let’s start our State Machine logic by making a script that can be used by the Buttons when the player clicks on them. Let’s start with the Button that is on the Button Canvas that says Start. Add the following function to your ChestAnimControls.cs script:
    // Called by player interactions (Waiting On Player)
    public void PlayerInputTrigger(string triggerString)
    {
        theStateMachine.SetTrigger(triggerString);
    }

    This function will trigger the State Machine’s trigger parameter specified by the string sent as an argument.

  13. 我们需要从“开始”按钮调用该函数,因此将以下On Click()事件添加到按钮画布上的按钮
  14. We will need to call that function from the Start button, so add the following On Click () Event to the Button on the Button Canvas:
图 14.70:按钮的 On Click() 事件

图 14.70:按钮的 On Click() 事件

Figure 14.70: The On Click() event of the Button

  1. 现在,我们需要创建一些按钮置于胸部打开画布转换的逻辑来自另外两个Waiting On Player状态。在Assets/Scripts/LootBox文件夹中创建一个名为OpenCloseButton.cs的新脚本
  2. Now, we will need to create some logic that will have the Button on Chest Open Canvas transition from the other two Waiting On Player states. Create a new script called OpenCloseButton.cs in the Assets/Scripts/LootBox folder.
  3. 使用以下命令将UnityEngine.UI命名空间添加到OpenCloseButton脚本:
    使用 UnityEngine.UI;
  4. Add the UnityEngine.UI namespace to the OpenCloseButton script with the following:
    using UnityEngine.UI;
  5. 现在,将以下代码添加到OpenCloseButton脚本中:
    文本按钮文本;
    动画师chestAnimController;
    无效唤醒()
    {
        按钮文本 = transform.GetComponentInChildren<文本>();
        chestAnimController = Camera.main.GetComponent<Animator>();
    }
    公共无效打开或关闭()
    {
        Debug.Log(“点击”);
        if (buttonText.text == “打开”)
        {
            chestAnimController.SetTrigger(“打开箱子”);
            设置文本(“关闭”);
        }
        别的
        {
            chestAnimController.SetTrigger(“关闭胸部”);
            设置文本(“打开”);
        }
    }
    公共无效设置文本(字符串设置文本到)
    {
        按钮文本.文本 = 设置文本到;
    }

    OpenOrClose()函数将由按钮的On Click ()事件调用。Chest Open Canvas上的按钮将用于打开和关闭箱子。它将根据按钮上写的当前文本设置适当的触发器,并使用SetText()函数将其文本更改为“打开”“关闭”

  6. Now, add the following code to the OpenCloseButton script:
    Text buttonText;
    Animator chestAnimController;
    void Awake()
    {
        buttonText = transform.GetComponentInChildren<Text>();
        chestAnimController = Camera.main.GetComponent<Animator>();
    }
    public void OpenOrClose()
    {
        Debug.Log("click");
        if (buttonText.text == "Open")
        {
            chestAnimController.SetTrigger("OpenChest");
            SetText("Close");
        }
        else
        {
            chestAnimController.SetTrigger("CloseChest");
            SetText("Open");
        }
    }
    public void SetText(string setTextTo)
    {
        buttonText.text = setTextTo;
    }

    The OpenOrClose() function will be called by the button›s On Click () Event. The Button on the Chest Open Canvas will be used to open and close the chest. It will set the appropriate trigger based on the current text written on the button and will change its text to "Open" or "Close" with the SetText() function.

  7. 添加OpenCloseButton脚本作为胸部打开画布按钮的组件
  8. Add the OpenCloseButton script as a component to the Button on Chest Open Canvas.
  9. 现在,添加On Click()事件添加到Chest Open Canvas上的按钮上
  10. Now, add the following On Click () Event to the Button on Chest Open Canvas:
图 14.71:按钮的 On Click() 属性

图 14.71:按钮的 On Click() 属性

Figure 14.71: The On Click() property of the Button

  1. 如果您现在玩游戏并单击“开始”按钮,则只会发生 Canvas 淡入的情况。这是因为我们还没有在ChestOpeningStateMachine动画器中设置AnimationComplete触发器。我们需要另一个脚本来设置此触发器。在Assets/Scripts/LootBox文件夹中创建一个名为AnimationControls的新脚本
  2. If you play the game now and click on the Start button, all that happens is that the Canvas fades in. This is because we still haven’t done anything to set the AnimationComplete trigger in the ChestOpeningStateMachine Animator. We will need another script to set this trigger. Create a new script called AnimationControls in the Assets/Scripts/LootBox folder.
  3. 调整AnimationControls类内的代码,如下所示:
    动画师chestAnimController;
    无效唤醒()
    {
        chestAnimController = Camera.main.GetComponent<Animator>();
    }
    // 在动画的最后一帧上调用动画事件
    公共无效ProceedStateMachine()
    {
        chestAnimController.SetTrigger(“动画完成”);
    }
  4. Adjust the code within the AnimationControls class, as follows:
    Animator chestAnimController;
    void Awake()
    {
        chestAnimController = Camera.main.GetComponent<Animator>();
    }
    // Call as an animation event on the last frame of animations
    public void ProceedStateMachine()
    {
        chestAnimController.SetTrigger("AnimationComplete");
    }
  5. 上面的代码只是使用ProceedStateMachine()函数设置AnimationComplete触发器

    此触发器用于允许每个单独的动画在开始下一个状态之前完全播放。为了确保在整个动画播放完毕之前不设置动画触发器,我们将使用动画事件在必要动画中的适当时间调用ProceedStateMachine()函数

    当您使用动画事件时,要调用的函数必须位于与动画附加到同一对象的脚本上。我们希望在Chest Open CanvasChestCoinHeartPowerUpChest Open Canvas上的按钮上的动画结束时调用该函数。因此,将AnimationControls脚本作为组件添加到每个动画中。

  6. The preceding code simply sets the AnimationComplete trigger with the ProceedStateMachine() function.

    This trigger is used to allow each of the individual animations to fully play before the next state is started. To make sure that the animation trigger isn’t set until the entire animation has played, we’ll use Animation Events to call the ProceedStateMachine() function at the appropriate time within the necessary animations.

    When you use an Animation Event, the function you want to call must be on a script attached to the same object as the animation. We want the function to be called at the end of animations on Chest Open Canvas, Chest, Coin, Heart, PowerUp, and the Button on Chest Open Canvas. Therefore, add the AnimationControls script as a component to each of them.

  7. 现在,我们需要将ProceedStateMachine()函数作为动画事件添加到各种动画。选择Chest Open Canvas并查看其CanvasGroupFadeIn动画。在其最后一个动画帧上,右键单击顶部深灰色时间轴区域并选择Add Animation Event。在 Inspector 中,从下拉菜单中选择ProceedStateMachine()。它将是列表中的最后一个函数。现在,当您将鼠标悬停在最后一个关键帧上方时,应该会看到一个白色标志,上面写着ProceedStateMachine
  8. Now, we will need to add the ProceedStateMachine() function as an Animation Event to the various animations. Select Chest Open Canvas and view its CanvasGroupFadeIn animation. On its last animation frame, right-click on the top dark gray area of the timeline and select Add Animation Event. In the Inspector, select ProceedStateMachine() from the dropdown menu. It will be the very last function in the list. You should now have a white flag above the last keyframe that says ProceedStateMachine when you hover over it:
图 14.72:将 ProceedStateMachine 事件添加到动画

图 14.72:将 ProceedStateMachine 事件添加到动画

Figure 14.72: Adding the ProceedStateMachine Event to the animation

  1. 如果您现在玩游戏并点击“开始”按钮,画布将淡入,宝箱将飞入。状态机将停留在ChestFlyingIn动画上。要完成动画序列,请将ProceedStateMachine()函数作为动画事件添加到以下每个动画中:ChestFlyingInChestOpeningCoinPoppingHeartPoppingPowerUpPop
  2. If you play the game now and click on the Start button, the Canvas will fade in and the chest will fly in. The State Machine will stay on the ChestFlyingIn animation. To have the animation sequence finish out, add the ProceedStateMachine() function as an Animation Event to each of the following animations: ChestFlyingIn, ChestOpening, CoinPopping, HeartPopping, and PowerUpPop.
  3. 现在玩游戏几乎可以正常工作。有一点重播动画序列时,物品从箱子中弹出的时间存在问题。目前, AnimationComplete触发器存在一些问题。我们需要在任何“等待玩家”状态开始时都取消设置该触发器。否则,当状态机重新启动时,它将被设置为true,从而导致一些时间问题。为了解决这个问题,我们需要一个状态机行为。选择“等待玩家(显示箱子)”状态,并在其检查器中创建并添加一个名为ResetTriggers 的新状态机行为。请记住,每当您创建新的状态机行为时,它都会添加到Asset文件夹中,因此请将其移动到Asset/Scripts/LootBox文件夹。
  4. Playing the game now almost works the way it should. There’s a bit of an issue with the timing of the items popping out of the chest when you replay the animation sequence. Currently, there is a bit of a problem with the AnimationComplete trigger. We need that trigger to definitely be unset whenever any of the Waiting On Player states start. Otherwise, it will be set to true when the State Machine restarts, causing some timing issues. To fix this, we need one more State Machine Behaviour. Select the Waiting on Player (Show Chest) state and create and add a new State Machine Behaviour called ResetTriggers in its Inspector. Remember that whenever you create a new State Machine Behaviour, it is added to the Asset folder, so move it to the Asset/Scripts/LootBox folder.
  5. 我们希望AnimationComplete触发器参数在“等待玩家”状态开始时重置,因此请更改ResetTriggers类中的代码,如下所示:
    // 当转换开始并且状态机开始评估此状态时,将调用 OnStateEnter
    覆盖公共无效OnStateEnter(Animator动画师,AnimatorStateInfo stateInfo,int layerIndex)
    {
        动画师.ResetTrigger(“动画完成”);
    }
  6. We want the AnimationComplete Trigger Parameter to reset whenever a Waiting On Player state starts, so change the code in the ResetTriggers class, as follows:
    // OnStateEnter is called when a transition starts and the state machine starts to evaluate this state
    override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        animator.ResetTrigger("AnimationComplete");
    }
  7. 现在,将ResetTriggers状态机行为添加到所有三个等待玩家状态。
  8. Now, add the ResetTriggers State Machine Behaviour to all three of the Waiting on Player states.

现在玩游戏时动画序列应该可以正确触发,除了粒子系统之外的所有内容,这将在下一章中介绍

Playing the game now should have the animation sequence firing correctly, with everything but the particle system, which will be covered in the next chapter.

概括

Summary

在 Unity 中,为 UI 元素制作动画与为任何其他 2D 对象制作动画并无太大区别。因此,本章简要概述了动画。本章还提供了使用状态机和动画事件创建复杂动画的工作流程示例。我们可以用动画做很多事情,本章中的示例向您展示了在为 UI 制作动画时可以使用的许多技术。希望这些示例能为您提供足够的技术变化,以便您能够确定将来如何制作自己的动画

Animating UI elements is not significantly different from animating any other 2D object in Unity. Therefore, this chapter offered a brief overview of animation. This chapter also offered an example of the workflow for creating complex animations utilizing a State Machine and Animation Events. There’s a lot we can do with animations, and the examples in this chapter showed you many of the techniques you can use while animating UI. Hopefully, these examples will provide you with enough variation of technique that you can determine how to make your own animations in the future.

在下一章中,我们将讨论如何在UI前渲染粒子效果。

In the next chapter, we will discuss how to render particle effects in front of the UI.

15

15

用户界面中的粒子

Particles in the UI

粒子效果是一种有趣且有吸引力的方式,可以为您的游戏增添“活力”。 Unity 引擎中的粒子系统为您提供了制作各种有趣效果(如火花、烟雾、火焰等)所需的工具。本章将讨论如何在UI 中使用粒子效果。

Particle effects are a fun and attractive way to add “juice” to your game. The Particle system within the Unity Engine provides you with the tools needed to make all sorts of interesting effects like sparkles, smoke, fire, and so on. This chapter will discuss how you can use particle effects within your UI.

在本章中,我们将讨论以下主题:

In this chapter, we will discuss the following topics:

  • 在UI中显示粒子效果的方法
  • Methods for displaying particle effects in your UI
  • 为战利品箱动画添加飞星
  • Adding flying stars to our loot box animation

本书是关于 UI 的,而不是粒子效果。由于粒子效果的复杂性,我不会介绍所有涉及的设置以及使用粒子效果的各种复杂性。我将引导您完成创建单个粒子效果的步骤,但不会进一步深入介绍创建粒子效果的过程。

This book is about UI, not particle effects. Due to the complex nature of particle effects, I will not be covering all the settings involved and the various intricacies of using particle effects. I will walk you through the steps to create a single particle effect but will not dive further into the process of creating particle effects.

技术要求

Technical requirements

您可以在此处找到本章节的相关代码和资产文件https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2015

You can find the relevant codes and asset files of this chapter here: https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2015

用户界面中的粒子

Particles in the UI

在 UI 中使用粒子是一种热门话题。似乎几乎每个带有战利品箱的手机游戏都使用粒子,但没有标准化的方法来实现它们。尝试在 UI 中使用粒子的问题在于,粒子在渲染模式设置为屏幕空间 -覆盖的画布上的 UI 后面渲染。

Using particles in UI is a hot topic. It seems like nearly every mobile game with loot boxes uses particles, but there is no standardized way to implement them. The problem with trying to use particles in the UI is that particles render behind UI on Canvases that have their Render Mode set to Screen Space - Overlay.

图 15.1:画布后面的粒子渲染

图 15.1:画布后面的粒子渲染

Figure 15.1: Particles rendering behind a Canvas

上面的屏幕截图显示了我们工作示例中的两个 UI Canvas 和一个粒子系统(白点)。粉色背景位于Background Canvas上,使用Screen Space - Camera进行渲染。圆形仪表和带有猫的 Panel 位于HUD Canvas上,使用Screen Space - Overlay进行渲染。粒子在 Background Canvas 前面和HUD Canvas后面进行渲染。但是,我希望粒子也将显示在HUD 画布前面

The preceding screenshot shows two UI Canvases from our working examples and a particle system (the white dots). The pink background is on the Background Canvas, which renders with Screen Space - Camera. The circular meter and the Panel with the cat are on the HUD Canvas, which renders with Screen Space - Overlay. The particles are rendering in front of the Background Canvas and behind the HUD Canvas. However, I want the particles to be displayed also in front of the HUD Canvas.

这个问题有几种解决方案。我首选的两个解决方案是以下任一方案:

There are a few solutions to this problem. My two preferred solutions are either of the following:

  • HUD Canvas上的Render Mode改为Screen Space - Camera,并调整其和粒子的排序顺序,使粒子出现在最前面
  • Change the Render Mode on HUD Canvas to Screen Space - Camera and adjust the sorting order of it and the particles to make the particles appear in front
  • 使用第二个相机和渲染纹理在画布上的原始图像上显示粒子
  • Use a second Camera and a Render Texture to display the particles on a Raw Image in the Canvas

这两种方法各有优缺点。第一种方法是最简单的。它允许您在仅对场景进行一两处修改的情况下在 UI 前查看粒子。但是,使用屏幕空间 - 相机作为 UI 的渲染模式可能对您的项目不切实际。如果您在游戏中编辑相机的属性,UI 的属性将被更改。此外,在设置好一切后更改画布的渲染模式可能会导致您的 UI 停止按您最初预期的方式显示。

There are benefits and downfalls to both methods. The first method is by far the easiest. It allows you to view particles in front of your UI with only one or two modifications to your scene. However, using Screen Space - Camera for your UI’s Render Mode may not be practical for your project. If you edit the properties of the Camera in your game, the properties of the UI will be changed. Additionally, changing the Render Mode of your Canvas after you have already set everything up can cause your UI to stop displaying the way you initially intended.

第二种方法实现起来并不复杂,但确实需要比第一种方法更多的工作。它的主要优点是您可以在使用屏幕空间 - 覆盖渲染的画布上在 UI 前面渲染粒子。除了需要更多工作来设置之外,它的主要缺点是,您可能必须对两个摄像头要渲染的内容做出一些复杂的决定,并且可能会稍微影响性能。本章示例部分讨论了涵盖此方法的示例。

The second method isn’t terribly complicated to implement but does require more work than the first. Its main benefit is that you can render particles in front of UI on Canvases that render with Screen Space - Overlay. Its main downfall, other than needing more work to set up, is that you may have to make some complicated decisions about what your two cameras are going to render, and may slightly affect performance. An example covering this method is discussed in the Examples section of this chapter.

这个问题还有其他解决方案,每个解决方案都比下一个更复杂(或更昂贵),您选择做什么取决于您的项目。有些项目完全放弃粒子,而是使用 After Effects 等软件将其粒子预渲染为精灵表。有些项目使用 Asset Store 中提供的资源,而其他项目则完全使用脚本和着色器。虽然我无法预见我提出的第二个解决方案不适用于您的项目的任何原因,但您的项目肯定有可能存在我没有考虑到的极端情况。如果是这样的话,希望您能够以最小的努力修改我的解决方案以完成您的项目。

There are other solutions to this problem, each more complicated (or costly) than the next, and what you choose to do depends on your project. Some projects forego particles entirely and pre-render their particles as sprite sheets using software, such as After Effects. Some projects use assets available in the Asset Store, and others handle everything entirely with scripts and shaders. Although I can’t foresee any reason why the second solution I proposed will not work for your project, it’s certainly possible that your project has a fringe case I am not considering. Hopefully, if that is the case, you will be able to modify my solution with minimal effort to work on your project.

我给你的最佳建议是尽早决定是否要在 UI 中使用粒子。如果你知道要使用它们,请提前规划你的 UI 布局和相机设置。此外,如果你想使用第一种方法,那就去做吧

My best advice to you would be to decide early whether you will use particles in your UI. If you know you are going to use them, plan ahead with your UI layouts and camera setups. Also, if you want to use the first method, do it.

Unity 没有实现此标准方法,这实在太糟糕了。我认为有一天会有一个预构建的 UI Particle 对象,使该过程既简单又高效。

It’s really too bad that there is no standard method for this implemented by Unity. I assume that one day there will be a pre-built UI Particle object that will make the process both simple and performant.

示例

Examples

对于本章中的示例,我们将在第 14 章中创建的动画中添加战利品箱打开时发生的粒子效果

For the examples in this chapter, we will add to the animations created in Chapter 14 to add a particle effect that occurs when the loot box opens.

创建在 UI 中显示的粒子系统

Creating a Particle System that displays in the UI

让我们创建一个粒子系统当箱子打开时会弹出。如本章前面所述,我在 UI 前面显示粒子的两种首选方式是使用屏幕空间 - 相机作为画布渲染模式或使用渲染纹理。由于第二种选择更复杂,因此值得举一个例子。您会注意到我们的画布都将其渲染模式设置为屏幕空间 - 覆盖,因此使用渲染纹理是当前项目设置方式的最佳方法。

Let’s create a particle system that will pop when the chest opens up. As stated earlier in this chapter, my two preferred ways of displaying particles in front of UI are to either use Screen Space - Camera as the Canvas Render Mode or to use a Render Texture. Since the second option is more complicated, it merits an example. You’ll notice that our Canvases all have their Render Modes set to Screen Space - Overlay, so using a Render Texture is the best method for the way the project is currently set up.

我们将创建一个粒子系统,通过第二个摄像机将其渲染为纹理,然后将该纹理显示在UI中的原始图像上。

We will create a particle system that is rendered to a texture via a second camera and then have that texture displayed on a Raw Image within the UI.

要创建粒子系统,请完成以下步骤。我们将在下一节中将其显示在 UI 中

To create a particle system, complete the following steps. We will work on displaying it in the UI in the next section:

  1. 我们要做的第一件事是创建用于粒子的材质。在Assets文件夹中创建一个名为Textures and Materials 的新文件夹。
  2. The first thing we will need to do is create a material that will be used for the particle. Create a new folder in the Assets folder named Textures and Materials.
  3. 在新文件夹中单击右键,然后选择创建|材质。将新材质命名为StarsMaterial
  4. Right-click within the new folder and select Create | Material. Name the new material StarsMaterial.
  5. 将StarsMaterial Shader 设置Unlit/Transparent
  6. Set the StarsMaterial Shader to Unlit/Transparent.
  7. starIcon精灵拖入其纹理槽中。
  8. Drag the starIcon sprite into its texture slot.
  9. 为了在 UI 对象前面显示粒子,我们需要第二个摄像头。使用Ctrl + D复制主摄像头,并将复制的摄像头重命名为UI粒子摄像头
  10. To display the particles in front of the UI objects, we will need a second Camera. Duplicate the Main Camera using Ctrl + D and rename the duplicate UI Particles Camera.
  11. 场景中只能有一个音频监听器,因此请删除新相机上的音频监听器组件
  12. You can only have one Audio Listener in the scene, so delete the Audio Listener component on the new camera.
  13. 移除AnimatorChest Anim Controls组件。
  14. Remove the Animator and Chest Anim Controls components.
  15. 您也不希望以后的任何代码认为这可能是主摄像头,因此将标签从MainCamera更改 Untagged
  16. You also won’t want any code later to think this might possibly be the Main Camera, so change the tag from MainCamera to Untagged.
  17. 该相机将仅用于显示我们将要制作的粒子弹出,因此我们不妨通过右键单击UI 粒子相机并选择效果|粒子系统,将粒子系统设为该相机的子项。
  18. This camera will be used only to display the particle pop we’re going to make, so we might as well make the particle system a child of this camera by right-clicking on the UI Particles Camera and selecting Effects | Particle System.
图 15.2:创建粒子系统作为 UI 粒子相机的子项

图 15.2:创建粒子系统作为 UI 粒子相机的子项

Figure 15.2: Creating a Particle System as a child of UI Particles Camera

笔记

Note

由于本书不是关于粒子系统而是关于 UI,因此我们不会花时间介绍粒子系统的每个属性。幸运的是,大多数属性都是不言自明的,摆弄各种属性可以让您了解它们可以做什么。因此,我不会介绍粒子系统的每个属性,而是只提供必要属性的屏幕截图。

Since this book isn’t about Particle Systems but about UI, we will not spend time going over every property of Particle Systems. Luckily, most are somewhat self-explanatory, and fiddling with the various properties lets you see what they can do. Therefore, rather than going through each property of the Particle System, I will simply provide screenshots of the necessary properties.

  1. 滚动到底部粒子系统组件,然后单击展开渲染器属性
  2. Scroll down to the bottom of the Particle System component and expand the Renderer property by clicking on it.
  3. 将StarsMaterial材质分配给Material属性。现在,这将使星星出现在粒子效果中。
  4. Assign the StarsMaterial material to the Material property. This will now make stars appear in your particle effect.
图 15.3:将 StarsMaterial 材质添加到 Renderer Material 属性

图 15.3:将 StarsMaterial 材质添加到 Renderer Material 属性

Figure 15.3: Adding StarsMaterial material to the Renderer Material property

  1. 更改将旋转 X转换为-90。粒子现在应该向上射出。
  2. Change the Transform Rotation X to -90. The particles should now shoot upward.
图 15.4:更改变换旋转

图 15.4:更改变换旋转

Figure 15.4: Changing the Transform Rotation

  1. 将位置 Z值更改10
  2. Change the Position Z value to 10.
  3. 将粒子系统的持续时间启动寿命启动速度属性更改如下:
  4. Change the Duration, Start Lifetime, and Start Speed properties of the Particle System as follows:
图 15.5:更新一些粒子系统设置

图 15.5:更新一些粒子系统设置

Figure 15.5: Updating some of the Particle System settings

  1. 选择“Start Size”的下拉列表,并选择“Random Between Two Constants”。将值设置为0 0.5
  2. Select the dropdown on Start Size and select Random Between Two Constants. Set the values to 0 and 0.5.
  3. 选择“开始旋转”下拉菜单,并选择“两个常数之间随机”。将值设置为-45 45
  4. Select the dropdown on Start Rotation and select Random Between Two Constants. Set the values to -45 and 45.
图 15.6:更新起始大小和起始旋转

图 15.6:更新起始大小和起始旋转

Figure 15.6: Updating Start Size and Start Rotation

  1. 将翻转旋转更改为1,将重力修改器 更改为0.5
  2. Change Flip Rotation to 1 and Gravity Modifier to 0.5.
图 15.7:更新粒子系统的设置

图 15.7:更新粒子系统的设置

Figure 15.7: Updating the settings of the particle system

  1. 展开Emission属性并添加带有加号的Burst
  2. Expand the Emission property and add a Burst with the plus sign.
图 15.8:添加爆发

图 15.8:添加爆发

Figure 15.8: Adding a burst

  1. 展开Shape属性并从形状下拉菜单中选择半球
  2. Expand the Shape property and select the Hemisphere from the Shape dropdown.
图 15.9:改变形状

图 15.9:改变形状

Figure 15.9: Changing the shape

  1. 选择Size over Lifetime属性。
  2. Select the Size over Lifetime property.
图 15.10:Size over Lifetime 属性

图 15.10:Size over Lifetime 属性

Figure 15.10: The Size over Lifetime property

  1. 选择速度旋转属性。
    图 15.11:按速度旋转属性

    图 15.11:按速度旋转属性

    现在,我们已经完成了粒子系统的属性设置。最后,我们将选择循环唤醒时播放,但现在,我们将取消选择它们,以便我们可以看到游戏进行时粒子系统不断播放。

  2. Select the Rotation by Speed property.

    Figure 15.11: The Rotation by Speed property

    And now, we’re done with the properties of the Particle System. Eventually, we will select Looping and Play on Awake, but for now, we will leave them deselected so that we can see the particle system constantly playing when the game is playing.

  1. 我们希望确保UI Particles Camera仅显示粒子系统,而Main Camera显示除粒子系统之外的所有内容。我们将使用Layers来实现这一点。

    选择图层下拉菜单并选择添加图层…

  2. We want to make sure that UI Particles Camera displays only Particle System and Main Camera displays everything but Particle System. We will accomplish this with Layers.

    Select the Layers dropdown menu and select Add Layer….

  3. 添加一个名为UI Particles的新用户层
  4. Add a new User Layer named UI Particles.
  5. 将UI 粒子分配给粒子系统Layer属性
  6. Assign UI Particles to the Layer property of Particle System.
  7. 现在,我们需要使用每个摄像机的Culling Mask属性指定它们将显示什么。

    设置UI Particles CameraCulling Mask属性为仅显示UI Particles,设置Main CameraCulling Mask属性为排除UI Particles

  8. Now, we need to specify to each of the cameras what they will be displaying using their Culling Mask property.

    Set the Culling Mask property of UI Particles Camera to only display UI Particles and the Culling Mask property of Main Camera to exclude UI Particles.

  9. 现在,让我们让UI Particles Camera渲染到纹理。在Textures and Materials文件夹中,右键单击并选择Create | Render Texture,并将其命名为StarPopRenderTexture
  10. Now, let’s have UI Particles Camera render to a texture. Within the Textures and Materials folder, right-click and select Create | Render Texture, and name it StarPopRenderTexture.
  11. 将其“尺寸”值更改为512 x 512
  12. Change its Size value to 512 x 512.
  13. 将StarPopRenderTexture纹理分配给UI Particles CameraCamera组件的Target Texture属性
  14. Assign the StarPopRenderTexture texture to the Target Texture property of UI Particles Camera’s Camera component.
图 15.12:分配目标纹理

图 15.12:分配目标纹理

Figure 15.12: Assigning the Target Texture

  1. 唯一要做的要做的就是在UI中显示渲染纹理。

    使用Create | UI | Canvas创建一个新的 UI Canvas 。将 Canvas 重命名为Particle Canvas

  2. The only thing left to do is have the render texture display in the UI.

    Create a new UI Canvas with Create | UI | Canvas. Rename the Canvas to Particle Canvas.

  3. 选择Particle Canvas后,选择Create | UI | Raw Image。将其重命名为Particle Renderer
  4. With Particle Canvas selected, select Create | UI | Raw Image. Rename it Particle Renderer.
  5. 将粒子渲染器宽度高度值分别更改为512512 ,以匹配StarPopRenderTexture属性
  6. Change the Width and Height values of Particle Renderer to 512 and 512, respectively, to match the properties of StarPopRenderTexture.
  7. StarPopRenderTexture分配给Raw Image组件的Texture属性粒子渲染
  8. Assign StarPopRenderTexture to the Texture property of the Raw Image component of Particle Renderer.
图 15.13:分配给 Texture 属性的渲染纹理

图 15.13:分配给 Texture 属性的渲染纹理

Figure 15.13: The Render Texture assigned to the Texture property

  1. 我们不希望该图像阻挡我们的鼠标点击,因此从Raw Image组件中取消选择Raycast Target
  2. We don’t want this image to block our mouse clicks, so deselect Raycast Target from the Raw Image component.
  3. 现在,我们只需确保Particle Canvas显示在其他两个 Canvas 的前面。在Particle CanvasCanvas组件上将Canvas Sort Order值设置为2
  4. Now, we just need to make sure that Particle Canvas displays in front of the other two Canvases. Set the Canvas Sort Order value to 2 on Particle Canvas’s Canvas component.
  5. 立即玩游戏,您现在应该可以看到显示场景的粒子。
  6. Play the game now, and you should now see the particles displaying the scene.
  7. 我们将粒子系统设置为持续播放,因此取消选择循环唤醒时播放以将值重置为应有的值
  8. We set the Particle System to be constantly playing, so deselect Looping and Play on Awake to reset the values to what they should be.

这就是在UI中显示的粒子系统的设置。

That’s it for setting up a particle system that displays in a UI.

定时播放粒子系统在我们的战利品箱动画中

Timing the Particle System to play in our loot box animation

现在粒子系统已设置为显示在 UI 前面,我们可以设置逻辑以使动画按正确的顺序触发。为此,请执行以下步骤:

Now that the particle system is set to display in front of the UI, we can set up the logic to have the animation trigger in the correct order. To do that, go through the following steps:

  1. 我们希望粒子系统当箱子打开时播放,所以我们必须编写一些代码来控制其行为。在Assets/Scripts文件夹中创建一个名为PlayParticles的新脚本
  2. We want the particle system to play when the chest opens, so we have to write some code to control its behavior. Create a new script called PlayParticles in the Assets/Scripts folder.
  3. 编辑PlayParticles类以包含以下代码:
    公共粒子系统星星;
    无效播放粒子(){
        如果(!星星正在播放){
            星星.播放();
        }
    }
  4. Edit the PlayParticles class to have the following code:
    public ParticleSystem stars;
    void PlayTheParticles() {
        if (!stars.isPlaying) {
            stars.Play();
        }
    }
  5. 这段代码的作用是使用PlayTheParticles()函数检查粒子系统当前是否正在播放。如果没有播放,则在函数运行时播放。
  6. All this code does is check whether the particle system is currently playing with the PlayTheParticles() function. If it is not playing, it plays when the function runs.
  7. 我们将通过Chest上的动画事件触发此功能。因此,将脚本作为组件添加到Chest 。
  8. We’ll have this function triggered via an Animation Event on the Chest. So, add the script to the Chest as a component.
  9. 将层次结构中的粒子系统分配给星星槽。
  10. Assign Particle System from the Hierarchy to the Stars slot.
  11. 将PlayTheParticles函数作为动画事件添加到ChestOpening动画的第一帧
  12. Add the PlayTheParticles function as an Animation Event on the very first frame of the ChestOpening animation.
图 15.14:粒子动画事件

图 15.14:粒子动画事件

Figure 15.14: The particle Animation Event

现在玩游戏应该会导致所有动画在适当的时间播放,并且粒子系统当箱子打开时显示。战利品箱动画教程到此结束!

Playing the game now should result in all animations playing at the appropriate times and the particle system displaying when the chest opens. And that concludes the loot box animation tutorial!

概括

Summary

乍一看,您似乎无法在设置为屏幕空间 - 叠加的 Unity UI 中使用 Unity 粒子。但是,有一些简单的技巧可以让您在 UI 中实现粒子效果并让粒子在它们前面渲染。

At first glance, it would appear that you cannot use Unity particles within Unity UI that is set to Screen Space - Overlay. However, there are a few simple tricks that let you implement particle effects in the UI and have the particles render in front of them.

在下一章中,我们将讨论如何使用世界空间画布渲染模式让 UI 元素直接出现在 Unity 场景中而不是屏幕上。

In the next chapter, we will discuss how to use the World Space Canvas Render Mode to have UI elements appear directly in your Unity scene rather than on the screen.

16

16

利用世界空间 UI

Utilizing World Space UI

在第 6 章中,我们讨论了可以分配给 Canvas 的三种不同的渲染模式。我们使用了屏幕空间-叠加和屏幕空间-相机,但还没有使用世界空间。如第 2 章所述,在世界空间中渲染的 UI 直接放置在场景中。我们已经讨论了世界空间 Canvas 渲染的属性,因此本章将仅介绍何时使用它以及实现示例。

In Chapter 6, we discussed the three different Render Modes you can assign to a Canvas. We’ve used Screen Space-Overlay and Screen Space-Camera but haven’t used World Space yet. As described in Chapter 2, UI rendered in World Space is placed directly in the scene. We’ve already discussed the properties of World Space Canvas rendering, so this chapter will just look at when to use it and examples of implementation.

在本章中,我们将讨论以下主题:

In this chapter, we will discuss the following topics:

  • 何时使用 World Space UI
  • When to use World Space UI
  • 使用 World Space UI时需要考虑的常规技术
  • General techniques to consider when working with World Space UI
  • 在 2D 游戏中使用世界空间画布创建相对于角色定位的状态指示器
  • Using World Space Canvases in a 2D game to create status indicators positioned relative to characters
  • 使用世界空间画布在3D 游戏中创建悬停在敌人头顶上的生命条
  • Using World Space Canvases to create health bars that hover over enemies’ heads in a 3D game

技术要求

Technical requirements

您可以在此处找到本章节的相关代码和资产文件https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2016

You can find the relevant codes and asset files of this chapter here: https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2016

何时使用 World Space UI

When to use World Space UI

您可能想要使用的原因有很多世界空间画布。使用此渲染模式的最常见原因如下:

There are many reasons you may want to use a World Space Canvas. The most common reasons for using this render mode are the following:

  • 为了更好地控制各个 UI 对象相对于场景中对象的位置
  • To have better control of individual UI objects’ positions in relation to objects in the scene
  • 旋转或弯曲UI 元素
  • To rotate or curve UI elements

例如,游戏Mojikara: Japanese Trainer使用世界空间画布来旋转面板,并将 UI 对象(如文本)附加到 3D 对象上。从以下屏幕截图中可以看到,左侧的面板在 3D 空间中略微旋转,因为它位于世界空间中空间画布:

For example, the game Mojikara: Japanese Trainer uses World Space Canvases to have rotated Panels and keep UI objects, such as Text, attached to 3D objects. As you can see from the following screenshot, the Panel on the left is rotated just slightly in 3D space, because it is on a World Space Canvas:

图 16.1:Mojikara:日语训练师(图片由 Intropy Games 的 Lisa Walkosz-Migliacio 提供)

图 16.1:Mojikara:日语训练师(图片由 Intropy Games 的 Lisa Walkosz-Migliacio 提供)

Figure 16.1: Mojikara: Japanese Trainer (image provided by Lisa Walkosz-Migliacio, Intropy Games)

另一个旋转 UI 示例可以在游戏Cloudbase Prime中找到,如以下屏幕截图所示。它还使用世界空间渲染来创建悬停在物体和角色上方的指示器。

Another example of rotated UI can be found in the game Cloudbase Prime, as shown in the following screenshot. It also used World Space rendering to create indicators that hover over objects and characters.

图 16.2:Cloudbase Prime(图片由 Floating Island Games 的 Tyrus Peace 提供)

图 16.2:Cloudbase Prime(图片由 Floating Island Games 的 Tyrus Peace 提供)

Figure 16.2: Cloudbase Prime (image provided by Tyrus Peace, Floating Island Games)

Cloudbase Prime中的所有 UI都已完成在世界空间画布上。这使开发人员能够创建炫酷的曲线 UI,如下所示:

All the UI in Cloudbase Prime was done on World Space Canvases. This allowed the developer to create cool curving UI, as follows:

图 16.3:Cloudbase Prime(图片由 Floating Island Games 的 Tyrus Peace 提供)

图 16.3:Cloudbase Prime(图片由 Floating Island Games 的 Tyrus Peace 提供)

Figure 16.3: Cloudbase Prime (image provided by Tyrus Peace, Floating Island Games)

在这里,您可以看到 UI 在编辑器中的外观以及它在玩家眼中的外观。这可以很好地展示 UI 的构建方式:

Here, you can see how the UI looks in the Editor versus how it looks to the player. This gives a nice peek at how the UI was built:

图 16.4:Cloudbase Prime(图片由 Floating Island Games 的 Tyrus Peace 提供)

图 16.4:Cloudbase Prime(图片由 Floating Island Games 的 Tyrus Peace 提供)

Figure 16.4: Cloudbase Prime (image provided by Tyrus Peace, Floating Island Games)

我建议你看看以下内容网站查看Cloudbase Prime实现 World Space UI 的更多方式,因为它们真的很漂亮:https ://imgur.com/a/hxNgL 。

I recommend checking out the following site to see more ways in which Cloudbase Prime has implemented World Space UI, as they are truly beautiful: https://imgur.com/a/hxNgL.

World Space UI 的另一个常见用途是模拟场景中的计算机屏幕和显示器。例如,我为朋友的 VR 游戏Cloud Rise构建了以下 UI 。通过将 World Space Canvas 放置在游戏屏幕的正上方来模拟显示器。然后我能够轻松地锚定和设置 UI 的动画;以同样的方式,我在屏幕空间中渲染了 UI

Another common usage of World Space UI is simulating computer screens and monitors within a scene. For example, I built the following UI for a friend’s VR game named Cloud Rise. The monitor was simulated by placing a World Space Canvas right on top of the in-game screen. I was then able to easily anchor and animate the UI; in the same way, I rendered the UI in Screen Space.

图 16.5:Cloud Rise(图片由 Bedhouse Games 的 Meredith Wilson 提供)

图 16.5:Cloud Rise(图片由 Bedhouse Games 的 Meredith Wilson 提供)

Figure 16.5: Cloud Rise (image provided by Meredith Wilson, Bedhouse Games)

一般情况下,VR 游戏的交互 UI 都在 World Space Canvas 上,因为玩家无法与屏幕交互。VR UI的用法是平面浮动面板或包裹面板。

In general, the interactive UIs of VR games are on World Space Canvases since the player cannot interact with the screen. Common usages of VR UI are flat floating Panels or wrapping Panels.

悬停指示器是迄今为止 World Space UI 最常见的用途;它们专门用于游戏内角色头顶上的生命条,如下面的Iris Burning屏幕截图所示:

Hovering indicators are by far the most common use of World Space UI; they are specifically used for health bars over the heads of in-game characters, as shown in the following screenshot of Iris Burning:

图 16.6:鸢尾花燃烧(图片由 William Preston、DCM Studios 提供)

图 16.6:鸢尾花燃烧(图片由 William Preston、DCM Studios 提供)

Figure 16.6: Iris Burning (image provided by William Preston, DCM Studios)

大多数人在想到世界空间 UI 时都会想到 3D 游戏,因为他们会想到看起来很远的UI ,但它也常用于 2D 游戏!管理和 RTS 游戏经常使用 UI 来创建按钮和进度条,其他 UI 元素则保持其位置与其交互的对象相关联。世界空间 UI 可以位于一个包含屏幕上所有项目的 Canvas 上,其中各个 UI 项目与它们所代表的项目的 2D 世界空间坐标相匹配,或者它们可以位于各自项目的单独 Canvas 上。我们将在本章末尾的示例中介绍如何使用世界空间 UI 创建 2D 游戏。

Most people think of 3D games when they think of World Space UI because they think of UI that appears far away, but it is commonly used in 2D games as well! Management and RTS games use UI quite frequently to create buttons and progress bars, and other UI elements maintain their position with the object they interact with. The World Space UI can be on one Canvas that encompasses all items on the screen, with individual UI items matching the 2D World Space coordinates of the items they represent, or they can be on individual Canvases of their respective items. We will cover how to create a 2D game using World Space UI in an example at the end of the chapter.

现在,让我们探索如何使用 World Space UI。

Now, let’s explore how to use World Space UI.

适当缩放画布中的文本

Appropriately scaling text in the Canvas

每当创建 Canvas 时,它都会以Screen Space - Overlay作为其Render Mode进行初始化。因此,当您将Render Mode属性更改为World Space时,Canvas 在场景中将变得非常大。

Whenever a Canvas is created, it is initialized with Screen Space - Overlay as its Render Mode. Therefore, when you change the Render Mode property to World Space, the Canvas will be huge in your scene.

当您将画布缩小到在场景中,文本可能会非常模糊或根本看不见。假设我们在屏幕空间 - 覆盖中创建了以下画布,但决定将其放在世界空间中:

When you scale down the Canvas to the appropriate size in the scene, the text will likely be super blurry or not visible at all. Let’s say we created the following Canvas in Screen Space - Overlay but decided to put it in World Space:

图 16.7:屏幕空间中的画布 - 覆盖

图 16.7:屏幕空间中的画布 - 覆盖

Figure 16.7: A Canvas in Screen Space - Overlay

将其转换为世界空间最初不会导致任何问题,但是一旦我们将其缩小到宽度4高度3(因为它最初是用 4:3 的宽高比屏幕创建的),文本就会消失!

Converting it to World Space doesn’t initially cause any problems, but once we scale it down to something like a Width of 4 and Height of 3 (since it was initially created with a 4:3 aspect ratio screen), the text will disappear!

图 16.8:屏幕空间中的画布 - 覆盖

图 16.8:屏幕空间中的画布 - 覆盖

Figure 16.8: A Canvas in Screen Space - Overlay

如果我将文本设置为允许水平溢出垂直溢出,你会发现它在比较时很大到画布上!在下面的截图中,中间的小矩形就是画布:

If I set the Text to allow Horizontal Overflow and Vertical Overflow you’ll see that it is huge when compared to the Canvas! In the following screenshot, the tiny rectangle in the middle is the Canvas:

图 16.9:带有巨大溢出文本示例的世界空间画布

图 16.9:带有巨大溢出文本示例的世界空间画布

Figure 16.9: World Space Canvas with huge overflowing text example

为了解决这个问题,并让它看起来像我们想要的那样,我们需要调整Canvas Scaler组件上的Dynamic Pixels Per Unit属性(最初在第 6 章中讨论过)。此属性最初设置1

To fix this, and to get it looking the way we want, we need to adjust the Dynamic Pixels Per Unit property on the Canvas Scaler component (initially discussed in Chapter 6). This property is initially set to 1.

通常,为了确定新的动态像素大小值,我会在缩小画布之前取其起始宽度(即905 ),将其除以新的宽度 4,然后将该除法输入到我的动态像素每单位属性中。(在框中输入实际除法905/4将执行计算。)

Usually, to determine the new Dynamic Pixel Size value, I take the starting Width of the Canvas before I scale it down, which is 905, divide it by the new Width 4, and enter that division in my Dynamic Pixels Per Unit property. (Typing the actual division 905/4 in the box will perform the calculation.)

图 16.10:动态像素/单位属性已调整

图 16.10:动态像素/单位属性已调整

Figure 16.10: Dynamic Pixels Per Unit property adjusted

然而,这一计算并没有得到这正是我想要的。因此,我增加了尺寸,直到看起来合适为止:

However, that calculation didn’t get the exact look I was looking for. So, I increased the size until it looked right:

图 16.11:第 16 章场景中的动态每单位像素属性示例

图 16.11:第 16 章场景中的动态每单位像素属性示例

Figure 16.11: Dynamic Pixels Per Unit property example in the Chapter16 scene

每次更改画布的宽度高度时,您都必须调整动态像素单位属性。减小画布的大小意味着增加动态像素单位属性,而增加画布的大小意味着减小单位动态像素属性的大小

Every time you change the Width and Height of the Canvas, you will have to adjust the Dynamic Pixels Per Unit property. Decreasing the size of the Canvas will mean increasing the Dynamic Pixels Per Unit property, and increasing the size of the Canvas will mean decreasing the size of the Dynamic Pixels Per Unit property.

这里有两个 Canvas,大小均为上图中的四分之一。在顶部 Canvas 中,我将 WidthHeight更改1.75。在底部 Canvas 中,我将Scale XScale Y更改 0.25

Here are two Canvases, both one-fourth the size of the one from the previous figure. In the top Canvas, I changed the Width and Height to 1 and .75. In the bottom Canvas, I changed the Scale X and Scale Y to 0.25:

图 16.12:宽度和高度变化示例

图 16.12:宽度和高度变化示例

Figure 16.12: Width and Height change examples

在第一个例子中,由于改变画布为四分之一大小,我在动态像素每单位属性中输入350*4,它自动为我计算了1400(我喜欢 Unity 在框中执行计算)。

In the first example, since I changed the Width and Height of the Canvas to one-fourth of the size, I typed 350*4 in the Dynamic Pixels Per Unit property, and it automatically calculated 1400 for me (I love that Unity performs calculations in the boxes).

但是,在第二个 Canvas 中,我不需要更改动态像素每单位大小,因为以这种方式使用Scale属性进行缩放不需要我更改它。

However, in the second Canvas, I did not have to change the Dynamic Pixels Per Unit size, because scaling with the Scale property in this way does not require me to change it.

从中可以得出的结论是,如果您的文本没有显示或者看起来非常模糊,请调整动态像素每单位属性,直到它看起来应该正常,或者通过调整其比例而不是其宽度 高度来缩放您的画布。

The takeaway from this is that if your text isn’t displaying or looks incredibly blurry, adjust the Dynamic Pixels Per Unit property until it looks the way it should, or scale your Canvas by adjusting its Scale and not its Width and Height.

文本缩放将是最重要的考虑使用 World Space UI,但让我们回顾一些其他重要主题。

Text scaling will be the most important consideration with using World Space UI, but let’s review some other important topics.

在世界空间中工作时的其他注意事项

Other considerations when working in World Space

在大多数情况下,使用 UI世界空间与在屏幕或相机空间中使用 UI 并无太大区别。不过,您需要记住一些事项

For the most part, working with UI in World Space isn’t much different than working with UI in Screen or Camera Space. There are a few things you have to keep in mind, though.

在处理 3D 场景时,你可能希望 UI 始终面向玩家,无论玩家如何转动相机——这被称为广告牌效果。你可以在Update()函数中对对象的变换使用一个简单的LookAt()函数来实现这一点

When working with 3D scenes, you may want your UI to always face the player, regardless of how the player turns the camera—this is known as a billboard effect. You can achieve this with a simple LookAt() function on the transform of the object in the Update() function:

变换.观察(2*变换.位置-相机.变换.位置);
transform.LookAt(2*transform.position-theCamera.transform.position );

您可以使用上述代码的变体,具体取决于您希望的旋转方式

You can use a variation of the preceding code, depending on how you want the rotation to behave.

3D 世界空间 UI 的另一个考虑因素是它与相机的距离。您可能希望 UI 仅在与相机有特定距离时才渲染,因为距离太远时可能难以看清

Another consideration with 3D World Space UI is the distance it is away from the camera. You may want to have UI only render when it is a specific distance from the camera as it may be difficult to see when it is too far away.

根据您的项目,使用世界空间画布可能会导致光线投射困难,从而导致与 UI 交互成为问题。Floating Island Games 的 Tyrus Peace 建议,如果您最终必须创建自己的光线投射系统,请创建自己的物理层,就像他使用Cloudbase Prime所做的那样,如本章前面所示

Depending on your project, using World Space Canvases may cause difficulties with Raycasting, making interacting with UI a problem. Tyrus Peace of Floating Island Games recommends creating your own physics layer if you end up having to create your own Raycasting system, as he did with Cloudbase Prime, shown earlier in the chapter.

示例

Examples

使用世界空间画布与使用屏幕和相机空间中的画布没有太大区别。世界空间画布有很多好处。如果你的场景中有一个对象,并且它的 UI 明确地与它的位置绑定,那么使用世界空间画布会很有帮助,这样 UI 就会跟随它,无论它在哪里。这样就无需尝试将对象的世界空间坐标转换为屏幕坐标,以确保 UI 始终与对象对齐。它还保证 UI 对象始终会根据对象的位置正确显示,即使屏幕的分辨率发生变化。在本章中,我将介绍世界空间画布的两种常见用途:一种在 2D 空间中,另一种在 3D 空间中。让我们从2D 空间开始

Working with World Space Canvases isn’t significantly different than working with Canvases in Screen and Camera Space. World Space Canvases offer many benefits. If you have an object that exists within your scene that has UI specifically tied to its location, it is helpful to use a World Space Canvas so that the UI follows it wherever it is. This removes the necessity of trying to convert the object’s World Space coordinates to Screen coordinates to ensure that the UI always lines up with the object. It also guarantees that the UI object will always display correctly with respect to the object’s location, even when the screen’s resolution changes. In this chapter, I will cover two common uses of World Space Canvases: one in 2D space and another in 3D space. Let’s begin with 2D space.

2D 世界空间状态指示器

2D World Space status indicators

在这个例子中,我们将开始一个新场景。为了让您不必构建场景,我们将从包含所有内容的资源包开始所需物品

For this example, we will start a new scene. For you to not have to build out the scene, we will start with an Asset package that includes all the required items.

我们将创建一个 UI,让角色的头顶弹出一个状态指示器。场景播放 3 秒后,角色头顶会出现一个状态指示按钮。玩家点击状态指示器后,会出现一个对话框。5 秒后,对话框会消失。状态指示器将在 10秒后重新出现。

We’ll create UI that allows a character to have a status indicator pop up above his head. After the scene has played for 3 seconds, a status-indicating button will appear over the character’s head. Once the player clicks on the status indicator, a dialog will appear. After 5 seconds, the dialog will disappear. The status indicator will re-appear 10 seconds later.

图 16.13:2D 世界空间 UI 示例

图 16.13:2D 世界空间 UI 示例

Figure 16.13: 2D World Space UI example

本例中使用的艺术作品来自https://opengameart.org/content/medieval-rts-120

The art used in this example was accessed from https://opengameart.org/content/medieval-rts-120.

要创建上例所示状态指示 UI,请完成以下步骤:

To create the status-indicating UI demonstrated by the previous example, complete the following steps:

  1. 导入第 16 章- 示例 1-Start.unitypackage包。此包包含一个带有背景图像的场景和一个名为Mage的 2D 精灵。包中包含的Assets/Scripts/MageInteractions.cs脚本控制状态指示器外观的计时器。此脚本需要两个 Canvas Group 项目 - theExclamationPointtheDialogBox - 并包含一个函数ShowTheDialogBox() ,可通过按钮的On Click()事件调用
  2. Import the Chapter 16 - Example 1-Start.unitypackage package. This package contains a scene with a background image and a 2D sprite named Mage. The Assets/Scripts/MageInteractions.cs script included with the package controls the timers on the appearance of the status indicator. This script requires two Canvas Group items—theExclamationPoint and theDialogBox—and contains a function, ShowTheDialogBox(), that can be called via a button’s On Click() event.
  3. 我们需要状态指示器并将对话框与场景中的Mage位置绑定。因此,我们将在World Space中创建一个 Canvas,它是Mage的子项

    右键单击层次结构中的Mage并添加 UI Canvas 作为 Mage 的

  4. We want the status indicator and the dialog to be tied to the position of the Mage within the scene. Therefore, we will create a Canvas that is a child of the Mage in World Space.

    Right-click on the Mage in the Hierarchy and add a UI Canvas as a child of the Mage.

  5. 选择新创建的Canvas。将Canvas组件上的Render Mode更改为World Space
  6. Select the newly created Canvas. Change the Render Mode on the Canvas component to World Space.
  7. 将主摄像机分配至事件摄像机插槽。
  8. Assign the Main Camera to the Event Camera slot.
  9. 由于此Canvas是Mage的子项,因此其坐标系统是相对于Mage的。要使其完美地定位在Mage上方,请将Rect Transform组件的Pos XPos Y更改 0
  10. Since this Canvas is a child of Mage, its coordinate system is relative to the Mage. To have it perfectly positioned over the Mage, change the Rect Transform component’s Pos X and Pos Y to 0.
  11. Canvas明显比Mage大。将Rect Transform组件的WidthHeight改为1 ,让尺寸更合理
  12. The Canvas is significantly bigger than the Mage. Make the size more reasonable by changing the Rect Transform component’s Width and Height to 1.
  13. 现在我们已经在场景中缩放并定位了Canvas周围的Mage ,我们可以向其中添加 UI 元素了。在层次结构中右键单击Canvas并创建一个 UI 按钮。将新按钮命名为Alert
  14. Now that we have the Canvas scaled and positioned in the scene around the Mage, we can add UI elements to it. Right-click on Canvas in the Hierarchy and create a UI Button. Name the new Button Alert.
  15. 通过设置Rect Transform组件的拉伸和锚点以完全拉伸至整个Canvas,调整Alert 的大小以匹配Canvas 。
  16. Resize Alert to match the Canvas by setting its Rect Transform component’s stretch and anchor to stretch fully across the Canvas.
  17. 将警报按钮的图像组件上的源图像更改为 UI旋钮图像。作为圆形比作为默认按钮精灵看起来更好:
  18. Change the Source Image on the Alert Button’s Image component to the UI Knob image. It looks better as a circle than as the default button sprite:
图 16.14:选择 UI 旋钮图像

图 16.14:选择 UI 旋钮图像

Figure 16.14: Selecting the UI Knob image

  1. 将AlertText子项更改为显示感叹号而不是Button
  2. Change the Text child of Alert to display an exclamation point instead of Button.
  3. 按钮的文本不是当前可见。要解决此问题,请将Canvas 的Canvas Scalar组件上的Dynamic Pixels Per Unit属性更改1000
  4. The Button’s text is not currently visible. To fix this, change the Dynamic Pixels Per Unit property on the Canvas Scalar component of Canvas to 1000.
  5. 移动警报按钮,使其位于法师的头上。
  6. Move the Alert Button so that it is positioned over the Mage’s head.
图 16.15:将感叹号移到法师的头上

图 16.15:将感叹号移到法师的头上

Figure 16.15: Moving the exclamation point over the Mage’s head

  1. 警报按钮添加一个画布组组件
  2. Add a Canvas Group component to the Alert Button.
  3. 将Canvas Group组件的Alpha属性设置为0 ,并将InteractableBlocks Raycasts属性都设置False
  4. Set the Canvas Group component’s Alpha property to 0 and set both the Interactable and Blocks Raycasts properties to False.
  5. 为警报按钮添加一个On Click()事件,该事件会调用ShowTheDialogBox()函数在附加到MageMageInteractions脚本上
  6. Give the Alert Button an On Click() event that calls the ShowTheDialogBox() function on the MageInteractions script attached to the Mage:
图 16.16:警报按钮的 On Click() 事件

图 16.16:警报按钮的 On Click() 事件

Figure 16.16: The On Click() event of the Alert Button

  1. 在 Hierarchy 中右键点击Canvas并创建一个 UI Text 对象。将新 Text对象命名为Dialog
  2. Right-click on Canvas in the Hierarchy and create a UI Text object. Name the new Text object Dialog.
  3. 通过设置Dialog 对象Rect Transform组件的拉伸和锚点以使其完全拉伸至整个Canvas 调整Dialog对象的大小来与Canvas匹配
  4. Resize the Dialog object to match the Canvas by setting its Rect Transform component’s stretch and anchor to stretch fully across the Canvas.
  5. 将对话框对象上的文本组件更改为Thanks!!!。此外,将文本居中对齐,并将水平溢出属性设置Overflow
  6. Change the Text component on the Dialog object to say Thanks!!!. Also, center-align the text and set the Horizontal Overflow property to Overflow.
  7. 移动对话框对象以便它位于法师的头顶,就像这样:
  8. Move the Dialog object so that it is positioned above the head of the Mage, like so:
图 16.17:对话框

图 16.17:对话框

Figure 16.17: The Dialog box

  1. 对话框文本对象添加一个Canvas Group组件
  2. Add a Canvas Group component to the Dialog Text object.
  3. 将Canvas Group组件的Alpha属性设置为0 ,并将InteractableBlocks Raycasts属性都设置False
  4. Set the Canvas Group component’s Alpha property to 0 and set both the Interactable and the Blocks Raycasts properties to False.
  5. 选择Mage,将Alert添加到感叹号属性中。将Dialog添加到对话框属性中。
  6. Select the Mage and add Alert to the The Exclamation Point property. Add Dialog to the The Dialog Box property.

如果您玩游戏,您将在 3 秒后看到感叹号按钮出现。单击按钮将使文本出现。尝试在场景中移动 Mage 角色。您会看到,无论他在哪里,感叹号按钮和文本都会出现在他的头上。这是一种非常有用的技术,可以创建与移动角色保持一致的 UI 元素。

If you play the game, you’ll see the exclamation point Button appear after 3 seconds. Clicking on the Button will make the Text appear. Try moving the Mage character around in the scene. You’ll see that no matter where he is, the exclamation point Button and Text appear over his head. This is a really helpful technique for creating UI elements that stay with moving characters.

我知道现在的例子有点无聊,但我建议使用前两章讨论的一些技巧,为感叹号添加一个漂亮的弹跳动画并让文本淡入淡出吨。

I know that the example is a bit boring the way it is now, but I recommend using some of the techniques discussed in the previous two chapters to add a nice bouncy animation to the exclamation point and have the Text fade in and out.

3D 悬浮健康条

3D hovering health bars

在 3D 场景中制作世界空间 UI 需要一点时间如果相机可以在 3D 空间中旋转和移动,则比在 2D 场景中制作世界空间 UI 的工作量更大。如果相机可以移动和旋转,则 UI 可能需要始终面向相机。否则,玩家将无法看到UI 元素。

Making World Space UI in a 3D scene takes a little more work than making World Space UI in a 2D scene if the camera can be rotated and moved throughout the 3D space. If the camera can move and rotate, the UI likely needs to constantly face the camera. Otherwise, the player will not be able to see the UI elements.

在本例中,我们将再次创建一个新场景。为了让您不必从头开始构建场景,我们将从包含所有必需项目的资产包开始。

For this example, we will once again create a new scene. For you to not have to build the scene from scratch, we will start with an Asset package that includes all the required items.

我们将创建一个简单的悬浮生命条,始终面向摄像头。它还将接收点击,以便我们观察生命条的减少:

We’ll create a simple hovering health bar that constantly faces the camera. It will also receive clicks so that we can watch the health bar reduce:

图 16.18:宇航员的健康条悬停

图 16.18:宇航员的健康条悬停

Figure 16.18: Astronaut with a hovering health bar

本例中使用的艺术作品来自https://opengameart.org/content/space-kit

The art used in this example was accessed from https://opengameart.org/content/space-kit.

要创建一个始终面向摄像机并接收玩家点击输入的生命值条,请完成以下步骤:

To create a health bar that always faces the camera and receives player-click input, complete the following steps:

  1. 导入第 16 章- 示例 2 - Start.unitypackage包。此包包含一个 3D 角色面向相机的场景。相机上附加了一个简单的RotatingCamera脚本,该脚本使用鼠标围绕宇航员旋转相机。该包还包含一个附加到宇航员角色的ReduceHealth脚本。此脚本有一个函数ReduceHealthBar,当单击角色头顶的健康条时,我们将调用该函数
  2. Import the Chapter 16 – Example 2 - Start.unitypackage package. This package contains a scene with a 3D character facing the camera. The camera has a simple RotatingCamera script attached to it that rotates the camera around the astronaut with the mouse. The package also contains a ReduceHealth script that is attached to the astronaut character. This script has a function, ReduceHealthBar, that we will call when the health bar above the character’s head is clicked on.
  3. 我们希望生命值条与宇航员在场景中的位置相关联。因此,我们将创建一个 Canvas,它是世界空间中宇航员的子对象

    在层次结构中右键单击宇航员,然后添加 UI Canvas 作为宇航员的

  4. We want the health bar to be tied to the position of the astronaut within the scene. Therefore, we will create a Canvas that is a child of the astronaut in World Space.

    Right-click on the astronaut in the Hierarchy and add a UI Canvas as a child of the astronaut.

  5. 选择新创建的Canvas,并将Canvas组件上的Render Mode更改为World Space
  6. Select the newly created Canvas and change the Render Mode on the Canvas component to World Space.
  7. 将相机分配至事件相机插槽。
  8. Assign the Camera to the Event Camera slot.
  9. 由于此Canvas是astronaut的子项,因此其坐标系相对于astronaut。为了使其完美地定位在astronaut上方,请将Rect Transform组件的Pos XPos Y更改 0
  10. Since this Canvas is a child of astronaut, its coordinate system is relative to astronaut. To have it perfectly positioned over the astronaut, change the Rect Transform component’s Pos X and Pos Y to 0.
  11. Canvas明显比宇航员大。将Rect Transform组件的Width改为10Height改为1让尺寸更合理
  12. The Canvas is significantly bigger than the astronaut. Make the size more reasonable by changing the Rect Transform component’s Width to 10 and the Height to 1.
  13. 将画布放置在宇航员的头部上方:
  14. Position the Canvas so that it is above the head of the astronaut:
图 16.19:将画布缩放至宇航员上方

图 16.19:将画布缩放至宇航员上方

Figure 16.19: Scaling the Canvas over the astronaut

  1. 现在我们已经缩放了画布将其放置在宇航员周围的场景中,我们可以向其中添加 UI 元素。右键单击层次结构中的Canvas并创建一个 UI 按钮。将新按钮命名为Health Bar
  2. Now that we have the Canvas scaled and positioned in the scene around the astronaut, we can add UI elements to it. Right-click on Canvas in the Hierarchy and create a UI Button. Name the new button Health Bar.
  3. 通过设置其Rect Transform组件的拉伸和锚点以完全拉伸至整个Canvas 调整Health Bar 的大小以匹配Canvas
  4. Resize the Health Bar to match the Canvas by setting its Rect Transform component’s stretch and anchor to stretch fully across the Canvas.
  5. 将健康栏图像组件上的源图像更改为,以赋予它一个白色矩形作为图像。
  6. Change the Source Image on the Image component of the Health Bar to None to give it a white rectangle as an image.
图 16.20:向按钮添加空白图像

图 16.20:向按钮添加空白图像

Figure 16.20: Adding a blank image to the Button

  1. “健康栏”的子文本更改为“单击以减少我的生命值”
  2. Change the Text child of Health Bar to say Click to reduce my health.
  3. 将Text子组件上的Text组件的Horizo​​ntal OverflowVertical Overflow属性都设置为Overflow。这样您就可以看到文本当前的大小场景中渲染:
  4. Set both the Horizontal Overflow and Vertical Overflow properties of the Text component on the Text child to Overflow. This will allow you to see the size the text is currently rendering at in the scene:
图 16.21:生命值条的超大文本

图 16.21:生命值条的超大文本

Figure 16.21: The oversized text of the Health Bar

  1. 文本字体大小设置为10,并取消选择文本组件上的射线投射目标
  2. Set the Text’s Font Size to 10 and deselect Raycast Target on the Text component.
  3. 选择画布,将鼠标悬停在画布缩放器组件中的动态像素每单位属性上,直到您看到鼠标光标周围出现两个箭头。看到这些箭头后,单击并向右拖动。这使该属性像滑块一样工作,允许您将看到如何不断增加“动态像素/单位”属性来改变文本在场景中的渲染方式。 重复此操作直到文本适合画布
  4. Select the Canvas and hover over the Dynamic Pixels Per Unit property in the Canvas Scaler component until you see two arrows appear around your mouse cursor. Once you see those arrows, click, and drag to the right. This makes the property work like a slider, allowing you to see how increasing the Dynamic Pixels Per Unit property continuously changes the way the text renders in the scene. Do this until the text fits within the Canvas:
图 16.22:生命值条的超大文本

图 16.22:生命值条的超大文本

Figure 16.22: The oversized text of the Health Bar

笔记

Note

当尝试让文本在 3D 空间中看起来美观时,如果仅更改动态像素单位大小会导致文本不连贯,请更改属性,直到文本在场景中看起来非常清晰。然后,结合更改Rect Transform组件的Scale和Text 对象的Font Size来找到最佳点

When trying to get the text to look nice in 3D space, if only changing the Dynamic Pixels Per Unit Size results in choppy text, change the property until the text looks perfectly crisp in the scene. Then, use a combination of changing the Rect Transform component’s Scale and Font Size of the Text object to find the sweet spot.

  1. 右键点击Health Bar Button,添加一个 UI Image 作为子项。将新 Image 命名为Health Fill
  2. Right-click on the Health Bar Button and add a UI Image as a child. Name the new Image Health Fill.
  3. 通过设置其Rect Transform组件的拉伸和锚点以完全拉伸至整个Health Bar ,调整Health Fill 的大小以匹配 Canvas Health Bar
  4. Resize Health Fill to match the Canvas Health Bar by setting its Rect Transform component’s stretch and anchor to stretch fully across the Health Bar.
  5. 现在,改变锚点和旋转至左侧 伸展,以便其向左缩放
  6. Now, change the anchor and pivot to left stretch so that it will scale leftward.
图 16.23:向左伸展的锚点

图 16.23:向左伸展的锚点

Figure 16.23: Left-stretching anchor

  1. 在层级结构中重新定位Health Bar,使其位于Text上方。这样填充就会渲染在Text对象后面。
  2. Reposition the Health Bar in the Hierarchy so that it is above Text. This will have the fill render behind the Text object.
图 16.24:游戏对象的层次结构

图 16.24:游戏对象的层次结构

Figure 16.24: The Hierarchy of GameObjects

  1. Health FillImage组件上,将Color属性更改为红色,并取消选择Raycast Target属性。
  2. On the Image component of the Health Fill, change the Color property to red and deselect the Raycast Target property.
  3. 选择宇航员并Health Fill对象分配给ReduceHealth组件上的Health Fill属性
  4. Select the astronaut and assign the Health Fill object to the Health Fill property on the ReduceHealth component.
  5. Health Bar Button添加On Click()事件,该事件调用宇航员ReduceHealth脚本的ReduceHealthBar函数:
    图 16.25:生命值条的 OnClick() 事件

    图 16.25:生命值条的 OnClick() 事件

    现在玩游戏应该会导致当您单击“健康按钮”时“健康填充”减少其填充值

    图 16.26:健康栏按钮减少

    图 16.26:健康栏按钮减少

  6. Add an On Click() event to the Health Bar Button that calls the ReduceHealthBar function of the ReduceHealth script on the astronaut:

    Figure 16.25: The OnClick() event of the Health Bar

    Playing the game now should result in the Health Fill reducing its fill value when you click on the Health Bar Button:

    Figure 16.26: The Health Bar button reducing

  1. 现在,我们只需要在Canvas上添加一个广告牌效果。在Assets/Scripts文件夹中创建一个名为BillboardPlane的新脚本
  2. Now, we just need to add a billboard effect to the Canvas. Create a new script called BillboardPlane in the Assets/Scripts folder.
  3. 将BillboardPlane类的脚本更改为以下内容:
    公共相机theCamera;
    无效更新()
    {
        变换。查看(2 * 变换。位置 - 相机。变换。位置);
    }
  4. Change the script of the BillboardPlane class to the following:
    public Camera theCamera;
    void Update()
    {
        transform.LookAt(2 * transform.position - theCamera.transform.position);
    }
  5. BillboardPlane脚本附加到Canvas。
  6. Attach the BillboardPlane script to the Canvas.
  7. 将相机分配到BillboardPlane组件中的相机插槽
  8. Assign the Camera to the Camera slot in the BillboardPlane component.

如果你现在玩这个游戏,你会看到当你移动相机时,生命值条始终面向相机尝试更改场景中相机的变换位置,以更显著地观察LookAt()函数的工作情况

If you play the game now, you’ll see that as you move the camera around, the health bar always faces the Camera. Try changing the Transform position of the camera in the scene to see the LookAt() function work more drastically.

概括

Summary

世界空间UI 的实现与在相机或屏幕空间中渲染的 UI 并没有太大区别。将 UI 添加到世界空间可让您创建炫酷的效果,并让您更好地控制 UI 相对于场景中对象的位置。

World Space UI is not significantly different in its implementation than UI that renders in the Camera or Screen Space. Adding UI to your World Space gives you the ability to create cool effects and gives you more control over your UI’s position relative to objects in the scene.

在下一章中,我们将讨论如何优化Unity UI。

In the next chapter, we will discuss how to optimize Unity UI.

17

17

优化 Unity UI

Optimizing Unity UI

优化是我们用来确保游戏运行顺畅且帧率一致的过程。通过优化,我们首先找到游戏中降低游戏性能的资源,然后实施可提高性能的解决方案。

Optimization is the process that we use to make sure that our game runs smoothly and the framerate is consistent. Through optimization we first locate resources within our game that are reducing our game’s performance and then implement solutions that will improve that performance.

很多因素都可能导致游戏性能不佳或帧率低下。这可能包括未优化的照明、编写不当的脚本、庞大的资源以及不当的 UI 构造。由于这是一本关于 UI 的书,我们将只关注如何改进 UI 构造来提高性能。

There are lots of things that can cause a game to have poor performance or low framerate. This can include things like unoptimized lighting, poorly written scripts, large assets, and improper UI construction. Since this is a UI book, we’ll focus only on how improving your UI construction can improve your performance.

在本章中,我将讨论以下内容:

In this chapter, I will discuss the following:

  • 优化相关的关键术语和基本信息
  • Key terms and basic information related to optimization
  • Unity 中提供的工具概述,可帮助你确定游戏的性能
  • An overview of the tools provided within unity that can help you determine how performant your game is
  • 各类UI优化策略
  • Various optimization strategies for UI

在优化 UI 之前,您需要了解如何判断其性能是否良好。让我们回顾一下图形渲染的一些基本术语和原理。

Before you can optimize your UI, you need to learn how to tell if it is performant or not. Let’s review some basic terms and principles of graphics rendering.

笔记

Note

本章将仅介绍基本级别的性能分析,重点将更多地放在您可以做的事情上,以构建更好的 UI。如果在本章结束时,您想了解有关性能分析的更多信息,我建议您使用以下资源: https: //unity.com/how-to/best-practices-for-profiling-game-performance#gpu-bound

This chapter will only cover performance profiling on a basic level and its focus will be more on things you can do to build better UI. If, by the end of the chapter, you’d like more information on performance profiling, I suggest the following resource: https://unity.com/how-to/best-practices-for-profiling-game-performance#gpu-bound

优化基础知识

Optimization basics

正如我之前所说,优化是我们用来确保应用程序顺利运行的过程,帧率是一致的。我们希望优化我们的游戏,以确保所有玩家无论在什么条件下玩游戏都能获得相同的体验。因此,例如,如果我们制作的是 PC 游戏,我们希望确保每个玩家无论机器的性能如何都能获得相同的体验。我们还希望确保如果玩家在屏幕上渲染很多东西,游戏不会比在屏幕上渲染较少东西时滞后。

As I stated earlier, optimization is the process that we use to make sure that our application runs smoothly, and the framerate is consistent. We want to optimize our game to ensure that all players have the same experience regardless of the conditions in which they are playing. So, for example, if we are making a PC game, we want to make sure that every player has the same experience regardless of the power of their machine. We also want to make sure if a player has many things rendering on the screen, the game does not lag compared to when there were less things rendering on the screen.

我们不希望帧率降低,也不希望输入延迟。这对于游戏等游戏来说非常重要。对于第一人称射击游戏或平台游戏等游戏来说,输入延迟可能意味着玩家输掉比赛或掉下悬崖。

We do not want the frame rate to reduce, and we do not want the inputs to lag. This is extremely important for things like games. In something like a first-person shooter or platform this could a lag in input could mean a player loses a match or falls off a cliff.

让我们回顾一下讨论优化时经常听到的一些基本术语。首先,我们先来了解一下确定应用程序性能的常用指标

Let’s review some basic terminology that you hear often when discussing optimization. First, we’ll start with a common metric for determining an application’s performance.

帧速率

Frame Rate

帧率是我们衡量的一个指标应用程序的优化。这是一个很好的指标,因为它不仅仅是用户永远不会注意到的后台发生的事情。用户可以看到并注意到帧速率的变化,因此测量其性能可以衡量我们的用户如何体验我们的游戏。

Framerate is one metric in which we measure our application’s optimization. It’s a good metric, because it’s not just something that is happening in the background that the user never notices. Users can see and notice changes in framerate, so measuring its performance measures how our users experience our games.

帧速率可以用每秒帧数( fps ) 或毫秒来衡量。目标是拥有一致的帧速率无论游戏中发生什么。

Framerate can be measured in frames per second (fps) or time in milliseconds. The goal is to have a consistent framerate regardless of what is happening in the game.

当以每秒帧数来衡量帧速率时,每个单独的帧都会在一定时间内渲染出来。假设你的目标是 60 fps。你需要确保应用程序的所有方面都能以 60 fps 的速度运行。因此,你的目标是每秒拥有一致的帧数。因此,当我们设置 fps 基准时,这意味着我们希望我们的应用程序在所有部分都以该 fps 一致运行。我们不希望我们的游戏一开始以 60 fps 运行,然后在我们的UI 打开时下降。

When measuring framerate in frames per second, each individual frame renders out in a certain amount of time. Let’s say you have a goal of 60 fps. You would need to make sure that all aspects of your application can run at 60 fps. So, your goal is to have a consistent number of frames per second. Therefore, when we set a fps benchmark, that means we want our application to run at that fps consistently across all parts of the application. We don’t want our game to run at 60 fps at the beginning and then dip when our UI opens.

测量帧速率的另一种方法是以毫秒为单位的时间。每帧都需要一定的时间来渲染。我们希望每帧的时间尽可能短。每帧 10 毫秒相当于 60 fps,以毫秒为单位是一个不错的时间。

Another way to measure framerate is time in milliseconds. Every frame takes some amount of time to render. We want that amount of time to be as low as possible for each frame. 10 ms per frame is the equivalent of 60 fps and is a good time in milliseconds.

既然我们已经讨论过了什么是帧率,我们先来说说我们的电脑上的资源用在了哪里。

Now that we’ve discussed what frame rate is, let’s talk about where resources are used on our computer.

GPU 和 CPU

GPU and CPU

当尝试优化时在您的游戏中,您将调查游戏的资源,以确定使用最多资源的地方。您需要确定问题是否出在GPU 或 CPU。中央处理器( CPU ) 是计算机的大脑。图形处理器( GPU ) 负责渲染图像。我们的资源是在GPU还是在CPU上将决定优化方案。

When trying to optimize your game, you will investigate your game’s resources to determine where the most resources are being used. You will want to identify whether the issues are on the GPU or CPU. The Central Processing Unit (CPU) is the brain of the computer. The Graphics Processing Unit (GPU), is what renders images. Whether our resources are on the GPU or CPU will determine the optimization solution.

问题示例CPU 的问题在于指令太多,这意味着运行的脚本太多,或者脚本运行时间太长。GPU 的问题往往意味着渲染的东西太多。然而,CPU 仍然在渲染中发挥作用,因为 CPU 发出指令并告诉 GPU 要渲染什么。在讨论 GPU 和 CPU 时,你经常会听到术语“绘制调用”。绘制调用是指CPU 告诉 GPU 它需要在屏幕上绘制(或显示)某些内容。一般来说,您需要减少游戏的绘制调用次数

Examples of problems on the CPU are too many instructions, meaning too many scripts are running or the scripts take too long to run. Problems with the GPU tend to mean too many things are being rendered. The CPU does still play a part in rendering, however, as the CPU gives out the instructions and tells the GPU what to render. Often when discussing GPU and CPU, you’ll hear the term draw calls. A draw call is when the CPU tells the GPU it needs to draw (or display) something on the screen. In general, you want to reduce the number of draw calls your game makes.

我们将在本章的后续章节中更深入地讨论 GPU 和 CPU,以及我们在设计 UI 时做出的各种选择如何受到 GPU 和 CPU 的影响。

We’ll discuss GPU and CPU and how various choices we make designing our UI are affected by GPU and CPU more thoroughly in future sections of this chapter.

现在我们已经介绍了一些优化的基本概念,让我们回顾一下Unity提供的一些可以帮助我们进行优化的工具。

Now that we’ve covered some basic concepts of optimization, let’s review some tools provided by Unity that help us with optimization.

确定绩效的工具

Tools for determining performance

在确定你的游戏性能,您可以使用所谓的基准测试。基准测试可以让你了解你的应用程序在各种条件下的运行情况。次。在进行基准测试时,您会收集性能指标并建立基线或基准指标。然后,您将后续结果与该基准进行比较,以查看指标是否有所改善或恶化。这可以让您知道所做的更改是否影响了游戏的性能。

When determining your game’s performance, you may use what is called benchmarking. Benchmarking allows you to see how well your app is running at various times under given conditions. When benchmarking, you collect performance metrics and establish a baseline, or benchmark metric. You then compare subsequent results to that benchmark to see if the metric has improved or worsened. This lets you know if changes you have made have affected your game’s performance.

Unity 提供了一些工具,可以帮助你评估游戏的性能,具体方法如下:性能指标。现在让我们看看这些工具。

There are a few tools provided by Unity that can help you assess the performance of your game by providing you performance metrics. Let’s look at those tools now.

笔记

Note

了解环境和基准测试的内容。建议您在目标平台上进行基准测试。这样您就可以确定计划运行游戏的最低标准设备。有了更好的设备,您的游戏性能才会更好。

Be aware of the environment and what you are benchmarking. It’s recommended that you benchmark on your target platform. That way you can determine the minimum standard device you plan to run your game on. The performance of your game will only get better with a better device.

统计窗口

Statistics window

一个简单的方法来查看游戏表现是通过游戏视图的统计窗口(或统计窗口)

One simple way to view the performance of your game is through the Statistics window (or Stats windows) of the Game view.

图 17.1:分析统计窗口

图 17.1:分析统计窗口

Figure 17.1: Analyzing the Stats Window

如果您选择游戏视图右上角的“统计”按钮,您将看到有关项目的各种信息。在“图形”部分下,您将看到每秒帧数的帧速率以及以毫秒为单位的时间。这将根据每一帧不断变化。您还可以看到有关 CPU 主线程和渲染线程的信息。

If you select the Stats button in the top right corner of your Game view, you’ll see various information about your project. Under the Graphics section, you’ll see frame rate in both frames per second and time in milliseconds. This will continuously change as it is based on each frame. You also see information about the CPU main thread and render thread.

如果你使用帧速率作为基准测试中,您可以在此处查看帧速率值并了解它们随时间的变化情况。请记住,目标是保持一致性。

If you’re using frame rate as your benchmark, you can watch the framerate values here and see how they change over time. Remember, the goal is consistency.

笔记

Note

您可以在此处了解有关统计窗口的更多信息: https: //learn.unity.com/tutorial/working-with-the-stats-window-2019-3 ?uv=2019.4#

You can learn more about the stats window, here: https://learn.unity.com/tutorial/working-with-the-stats-window-2019-3?uv=2019.4#

统计信息窗口提供了一些关于游戏性能的基本信息。但是,如果你想要更深入地了解游戏的运行情况,您可以使用Unity Profiler。

The Stats window gives some at a glance basic information about your game’s performance. However, if you want a more in-depth breakdown of how your game is running, you can use the Unity Profiler.

Unity 分析器

Unity Profiler

Profiler 向您显示详细信息关于你的游戏性能。要查看 Profiler,请选择窗口|分析| Profiler。玩游戏时,您可以看到哪些项目导致了性能问题。

The Profiler shows you detailed information about your game’s performance. To view the Profiler, select Window | Analysis | Profiler. When you play the game, you can then see which items are responsible for performance issues.

图 17.2:查看 Unity Profiler

图 17.2:查看 Unity Profiler

Figure 17.2: Viewing the Unity Profiler

Profiler中,您可以仅选择 UI 模块来缩小每个单独的 UI 元素对性能的影响范围。

Within the Profiler, you can select the UI Modules only to narrow down how each of your individual UI elements are affecting your performance.

图 17.3:仅启用 UI Profiler 模块

图 17.3:仅启用 UI Profiler 模块

Figure 17.3: Enabling only the UI Profiler Modules

完成后,您可以看到信息关于每个帆布。

After doing so, you can see information about each individual Canvas.

图 17.4:观察 Profiler 中的 UI 对象

图 17.4:观察 Profiler 中的 UI 对象

Figure 17.4: Observing the UI Objects in the Profiler

笔记

Note

您可以在此处了解有关 Unity Profiler 的更多信息及其使用方法https://docs.unity3d.com/Manual/Profiler.xhtml

You can learn more about the Unity Profiler and how to use it here: https://docs.unity3d.com/Manual/Profiler.xhtml

如果你有兴趣游戏中的物品进行批处理后,您可以使用 Unity Frame Debugger。

If you’re interested in how items in your game are being batched, you can use the Unity Frame Debugger.

Unity 帧调试器

Unity Frame Debugger

框架调试器可以帮助你排除故障您的 UI 存在批处理问题。您可以通过窗口|分析|帧调试器访问帧调试器。

The Frame Debugger can help you troubleshoot batching issues with your UI. You can access the Frame Debugger via Window | Analysis | Frame Debugger.

图 17.5:查看框架调试器

图 17.5:查看框架调试器

Figure 17.5: Reviewing the Frame Debugger

启用 Frame Debugger 后,您可以看到各种渲染事件。单击它们将在游戏窗口中前进,为您提供事件的预览。这将向您显示哪些项目正在组合成批次。

Enabling the Frame Debugger will allow you to see various rendering events. Clicking on them will step forward in the Game window, giving you a preview of the event. This will show you which items are being combined into batches.

笔记

Note

您可以在此处了解有关 Frame Debugger 的更多信息https://docs.unity3d.com/Manual/frame-debugger-window.xhtml

You can learn more about the Frame Debugger here: https://docs.unity3d.com/Manual/frame-debugger-window.xhtml

现在我们已经讨论了优化的基础知识,我们可以开始讨论优化UI 的方法。

Now that we’ve discussed the basics of optimization, we can start talking about ways to optimize your UI.

Unity UI 优化基本策略

Basic Unity UI Optimization Strategies

请记住,每个 Canvas 都有自己的 Canvas Renderer 组件。Canvas 将所有元素组合成批并一起渲染。如果 Canvas 的几何图形需要重建,则该 Canvas 被视为脏的。优化 UI 的主要目标是减少 Canvas 或其元素被视为脏的次数,减少 Canvas 需要重新批处理的次数。考虑到这一点,让我们来看看一些优化Unity UI 的技巧。

Remember, each Canvas has its own Canvas Renderer component. Canvases combine all their elements into batches that are rendered together. A Canvas is considered dirty, if its geometry needs to be rebuilt. One of the main goals of optimizing UI is to reduce the number of times a Canvas or its elements are considered dirty, to reduce the number of times that the Canvas needs to be rebatched. With that in mind, let’s look at some techniques for optimizing Unity UI.

使用多个画布和画布层次结构

Using multiple Canvases and Canvas Hierarchies

每当一个元素如果画布上的元素被修改,画布将被视为脏元素,并向 GPU 发送绘制调用。如果画布上有多个项目,则需要重新分析画布上的所有项目,以确定最佳绘制方式。因此,更改画布上的一个元素需要 CPU 重建画布上的每个元素,这可能会导致 CPU 使用率突然飙升。因此,您应该将 UI 放在多个画布上。

Whenever an element on a Canvas is modified, the Canvas is considered dirty, and a draw call is sent to the GPU. If there are multiple items on the Canvas, all the items on the Canvas will need to be reanalyzed to determine how best they should be drawn. So, changing one element on the Canvas requires the CPU to rebuild every element on the Canvas, potentially causing a sudden surge in CPU usage. Due to this, you should put your UI on multiple Canvases.

在确定如何对画布进行分组时,请考虑画布上的项目需要更改的频率。将所有静态 UI 元素与动态项目分组到不同的画布上是一种很好的做法。这样可以避免在动态项目发生变化时重新绘制静态项目。此外,根据动态元素的更新时间将其拆分到画布中,尽量将同时更新的元素放在一起。

When determining how to group your Canvases, consider how often the items on the Canvas will need to be changed. It is good practice to group all static UI element on separate Canvases from items that are dynamic. This will stop the static items from having to be redrawn whenever the dynamic items change. Additionally, split dynamic elements into Canvases based on when they will update, trying to keep ones that update at the same time together.

尝试减少画布上的绘制调用时需要考虑的其他方面是元素 z 坐标、纹理和材质。根据这些属性对 UI 元素进行分组也可以减少与UI 相关的 CPU 使用率。

Other aspects to consider when attempting to reduce draw calls on Canvases are the elements z-coordinates, textures, and materials. Grouping UI elements based on these properties can also reduce your CPU usage with regards to UI.

尽量减少布局组的使用

Minimizing the use of Layout Groups

布局组非常当您需要适当间隔的 UI 时,这种方法非常有用;但是,它们效率极低。每当布局组中的元素发生变化时,该组中的每个元素都必须为每个具有布局组的父对象调用至少一个GetComponent。这使得嵌套布局组的性能非常低下。

Layout Groups are incredibly helpful when you want properly spaced UI; however, they are extremely inefficient. Whenever an element in a Layout Groups changes, every element in that group must call at least one GetComponent for each parent object that has a Layout Groups. This makes nested Layout Groups very non-performant.

为了避免这种情况,您可以简单地不使用布局组。相反,您可以使用锚点并编写自己的布局代码来动态放置项目。

To avoid this, you could simply not use Layout Groups. Instead, you could use Anchors and write your own layout code for dynamically placing items.

然而,完全避免使用它们并不一定对每个人来说都是最好的解决方案,所以如果你选择使用它们,请尝试避免嵌套布局组或包含大量子项的布局组。您还可以选择仅对动态元素使用布局组,并避免将其与静态元素一起使用。您还可以在动态 UI正确定位后立即禁用它们。

Avoiding them entirely isn’t necessarily the best solution for everyone, however, so if you do choose to use them, try to avoid nested Layout Groups or layout groups with very large amounts of children. You can also choose to use layout groups for dynamic elements only and avoid them with static elements. You can also disable them as soon as dynamic UI has been properly positioned.

适当隐藏物体

Hiding objects appropriately

如果你想隐藏元素Canvas,如果可能的话,最好禁用整个 Canvas 组件。请记住,Canvas 组件是渲染 UI 的组件,因此禁用 Canvas 组件将导致其停止渲染。建议您禁用 Canvas 组件,而不是简单地更改 Canvas 的可见性,因为即使 Canvas 组件不可见,它仍将继续进行绘制调用。禁用组件不会导致 Canvas 重建。这比启用和禁用 Canvas GameObject 更便宜,因为这将触发OnEnableOnDisable方法。

If you want to hide elements on a Canvas, it is best to disable the entire Canvas component if possible. Remember, the Canvas component is what renders the UI, so disabling the Canvas component will cause it to stop rendering. It is recommended you disable the Canvas component rather than simply changing the visibility of the Canvas, because the Canvas component will continue to make draw calls, even if it is not visible. Disabling the component does not cause the Canvas to rebuild. This is less expensive than enabling and disabling the Canvas GameObject, as that will trigger the OnEnable and OnDisable methods.

如果您想用 UI 隐藏游戏中的所有内容,例如,您有一个完全覆盖屏幕的暂停菜单,则应停止摄像机渲染游戏中除 UI 之外的任何内容。例如,如果您有一个完全覆盖屏幕的菜单,即使您看不到菜单后面的项目,它们仍会被渲染。

If you want to hide everything in your game with UI, for example you have a pause menu that completely covers your screen, you should stop the camera from rendering anything in your game other than the UI. For example, if you have a menu that completely covers the screen, even though you cannot see the items behind the menu, they are still being rendered.

适当地启用和禁用对象池

Appropriately time object pooling enabling and disabling

对象池是一种优化使用的技术减少可重复创建(或实例化)对象的次数。使用对象池时,您会在游戏开始时将一组禁用对象放入池中,然后,您无需在游戏进行时实例化这些对象,而是将它们从池中拉出。

Object pooling is an optimization technique used to reduce the number of times repeatable objects are created (or instantiated). When object pooling, you place a collection of disabled objects in a pool at the start of the game and then, rather that instantiating those objects while the game is playing, you pull them from the pool.

对象池是提高游戏性能的好方法,因为它通过创建可重用的对象集合来减少创建对象的次数。

Object pooling is a great way to improve the performance of your game, because it reduces the number of times an object has to be created by creating a collection of objects that can be reused.

笔记

Note

有关对象池概念的更多信息,请访问以下网站: https: //learn.unity.com/tutorial/introduction-to-object-pooling

For more information on the concept of object pooling, visit the following site: https://learn.unity.com/tutorial/introduction-to-object-pooling

由于重新设置 UI 对象会导致 Canvas 被标记为脏,因此在使用对象池时,您需要仔细安排禁用、启用和重新设置的时间。由于 Canvas 中元素的动态更改会导致 Canvas 向 GPU 发送绘制调用,因此在禁用项目之前或之后为其设置父级,可能会不必要地增加绘制调用次数。目标是不让池中的对象向 GPU 发送任何绘制调用。因此,池中的对象应始终只在禁用状态下作为池的父级。

Because reparenting UI objects causes a Canvas to be marked as dirty, you want to carefully time your disabling, enabling, and reparenting when using an object pool. Since dynamic changes to elements within a Canvas cause the Canvas to send a draw call to the GPU, inappropriately parenting items before or after disabling them, can double up your draw calls unnecessarily. The goal, is to not cause the objects in the pool to send any draw calls to the GPU. So, objects in the pool should only ever be parented to the pool in a disabled state.

如果你将物体放在对象池,禁用它,然后将其重新设置为池的父级。通过确保在将其放入池之前将其禁用,您将无需重建池。相反,如果您想从池中移除对象,请重新设置为它的父级,然后启用它。这也将消除池发送绘制调用的需要。

If you are placing an object in an object pool, disable it, then reparent it into the pool. By making sure it is disabled before being placed in the pool, you will remove the need for the pool to need rebuilding. Conversely, if you want to remove an object from the pool, reparent it, then enable it. This too will remove the need for the pool to send a draw call.

减少射线投射计算

Reducing Raycast computations

每个 UI 图像组件具有属性Raycast Target。默认情况下,此选项处于选中状态。如果您不希望 UI 元素阻止射线投射,请禁用此选项以减少射线投射检查。

Every UI Image component has the property Raycast Target. By default, this is selected. If you do not wish for your UI element to block raycasts, disable this to reduce raycasting checks.

图 17.6:Raycast Target 属性

图 17.6:Raycast Target 属性

Figure 17.6: The Raycast Target property

此外,如果您的 UI 根本无法交互,您可以删除 Graphic Raycaster 组件以消除其引起的不必要的计算。

Additionally, if your UI is not interactable at all, you can remove the Graphic Raycaster component to remove the unnecessary calculation it causes.

图 17.7:Graphic Raycaster 组件

图 17.7:Graphic Raycaster 组件

Figure 17.7: The Graphic Raycaster component

使用基本的 Unity UI本章介绍的优化策略将帮助你创建不会对 UI 产生负面影响的 UI游戏的表现。

Using the basic Unity UI optimization strategies covered in this chapter will have you well on your way to creating UI that does not inversely affect the performance of your game.

概括

Summary

在本章中,我们讨论了优化 Unity UI 的基础知识。我们回顾了 Unity 中优化的一些基本概念,研究了可用于评估游戏性能的工具,然后讨论了创建高性能 UI 的策略。

In this chapter, we discussed the basics of optimizing Unity UI. We reviewed some fundamental concepts of optimizing in Unity, looked at the tools that allow us to assess our game’s performance, and then discussed strategies for creating performant UI.

笔记

Note

本章仅重点介绍了如何通过 UI 提高游戏性能。但请记住,游戏的多个方面都可能导致其出现性能问题。有关如何提高游戏各个方面性能的良好资源,我建议查看 Unity 提供的有关优化的免费电子书:https://resources.unity.com/games/performance-optimization-e-book-console-pc

This chapter focused only on how to improve the performance of your game through UI. But remember there are multiple aspects of your game that can cause it to have performance issues. For a good resource on improving the performance of all aspects of your game, I suggest reviewing the free ebook provided by unity on optimization: https://resources.unity.com/games/performance-optimization-e-book-console-pc

在下一章中,我们将讨论 Unity 创建的一个新 UI 系统,称为新 Unity UI 工具包。它不仅可以在运行时创建 UI,还可以在编辑器中创建 UI。因此,它可以用来帮助您创建工具来改善您的工作流程。

In the next chapter, we’ll talk about a new UI system created by unity called the new unity UI toolkit. It can be used to create UI not only during runtime but also within the editor. Thus, it can be used to help you create tools to improve your workflow.

进一步阅读

Further reading

有关优化 UI 的更多信息,我推荐以下资源:

For more information on optimizing UI, I recommend the following resources:

第五部分:其他 UI 和输入系统

Part 5: Other UI and Input Systems

在本部分中,您将不再探索 Unity UI 系统,而是了解 Unity 提供的另外两个用于创建 UI 的系统 - UI Toolkit 和 IMGUI。您还将了解如何使用新输入系统处理输入。

In this part, you’ll step away from exploring the Unity UI system and look at the two other systems provided by Unity to create UI – UI Toolkit and IMGUI. You’ll also look at how to handle input with the New Input System.

本部分包含以下章节:

This part has the following chapters:

18

18

UI Toolkit 入门

Getting Started with UI Toolkit

到目前为止,本书的重点一直是Unity UI ( uGUI )。然而,Unity 一直在开发另一个可用于开发游戏 UI 的系统:UI Toolkit。它基于 Web 设计原则,旨在让您以比 uGUI 更灵活、更具扩展性的方式创建 UI。它通过将 UI 的设计和开发与场景和游戏对象分离来实现这一点,而是通过代码和样式表创建 UI — 就像在Web 设计中一样。

Up to this point, the focus of this book has been on Unity UI (uGUI). However, Unity has been developing another system in which you can develop UI for your game: the UI Toolkit. It is based on the principles of web design and is meant to allow you to create UI in a more flexible and extensible way than the uGUI. It accomplishes this by divorcing the design and development of UI from scenes and GameObjects and instead creates UI via code and style sheets—just like in web design.

UI Toolkit 是一个完全不同的系统,因此如果您习惯使用 uGUI 开发 UI,那么一开始使用它进行开发可能会感觉很不舒服。但是,如果您有使用.xhtml.css的经验,或者使用XML开发 Android 或 iOS 界面的经验,那么这一切都应该非常熟悉。

The UI Toolkit is an entirely different system, so beginning development with it may feel jarring at first if you are used to developing UI with uGUI. However, if you have experience with .xhtml and .css or developing Android or iOS interfaces using XML, this should all feel very familiar.

由于 UI Toolkit 是一个完全不同的系统,所采用的原理与本书其他章节完全不同,因此,要完整解释您可以使用它做的所有事情,就需要专门写一本教科书。因此,本章将为您提供开始使用该系统所需的基本信息,但不会全面讨论它的每个方面。如果您想进一步学习 UI Toolkit,我将在本章中为您提供其他资源供您查看。

Since the UI Toolkit is an entirely different system using entirely different principles than the rest of the chapters in this book, to fully explain all that you could do with it would merit a whole other textbook devoted purely to it. Therefore, this chapter will give you the basic information you need to get started with using the system, but not fully discuss every aspect of it. I will give you additional resources throughout this chapter to review if you’d like to go even further with your study of the UI Toolkit.

在本章中,我将讨论以下内容:

In this chapter, I will discuss the following:

  • UI 工具包概述以及如何安装它(如果您的Unity版本中还没有它)
  • An overview of the UI Toolkit and how to install it if it’s not already in your version of Unity
  • UI 工具包的各个部分共同协作,创建并设计界面
  • The various parts of the UI Toolkit that work together to create and style an interface
  • 什么是视觉元素以及 UI 工具包层次结构如何工作?
  • What are Visual Elements and how does the UI Toolkit Hierarchy work?
  • 如何使用 UI Builder 设计和布局 UI Toolkit 界面
  • How to use the UI Builder to design and layout UI Toolkit interfaces
  • 如何在 C# 中访问 UI Toolkit 构建的 UI
  • How to access UI Toolkit-built UI in C#
  • 在 Unity 编辑器中创建一个虚拟宠物,陪伴你并鼓励你
  • Creating a virtual pet that hangs out with you in your Unity Editor and encourages you
  • 使用 UI Builder 创建样式表和动画过渡
  • Using the UI Builder to create style sheets and animation transitions
  • 使用 Web 请求随机生成 UI 的图像和文本
  • Using web requests to randomly generate your UI’s images and text

笔记

Note

本章假设您对 HTML 和 CSS 有粗略的了解。但是,如果您对 HTML 和 CSS 有更丰富的经验,那么采用 UI Toolkit 系统将变得容易得多。

This chapter assumes you have a cursory knowledge of HTML and CSS. However, having more extensive experience with HTML and CSS will make adopting the UI Toolkit system significantly easier.

在我们深入了解 UI 工具包的内部工作原理之前,让我们先回顾一下它的用例以及何时使用它。

Before we jump into the inner workings of the UI Toolkit, let’s review its use cases and when you will use it.

技术要求

Technical requirements

您可以在此处找到本章节的相关代码和资产文件https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2018

You can find the relevant codes and asset files of this chapter here: https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2018

UI Toolkit 概述

Overview of UI Toolkit

您可能还记得第 5 章中提到,Unity 中总共有三种 UI 系统。到目前为止,本书的重点是 Unity UI (uGUI),它可用于制作游戏内(又称运行时)UI。但是,如果你想制作可以在编辑器中查看的 UI,则必须使用不同的系统。可用于创建编辑器的两个系统UI 是IMGUI(我们将在下一章讨论)和 UI Toolkit。但是,IMGUI 只能用于制作编辑器 UI,而 UI Toolkit 既可用于制作运行时 UI,也可用于制作编辑器 UI。

As you may recall from Chapter 5, there are three total UI systems that can be used within Unity. Up to this point, the focus of this book has been on Unity UI (uGUI), which can be used to make in-game (aka runtime) UI. However, if you want to make UI that can be viewed in your Editor, you will have to use a different system. The two systems that can be used to create Editor UI are IMGUI, which we will discuss in the next chapter, and the UI Toolkit. However, while IMGUI can only be used to make Editor UI, the UI Toolkit can be used to make both runtime and Editor UI.

图 18.1:比较三个 UI 系统

图 18.1:比较三个 UI 系统

Figure 18.1: Comparing the three UI systems

Unity 的目标是用新的 UI Toolkit 完全取代 uGUI 系统。然而,它仍处于开发阶段,并且具有 uGUI 的所有功能。例如,UI Toolkit 无法制作定位在 3D 世界中的 UI,就像我们在第 16 章中讨论的那样。它只能制作覆盖在屏幕顶部的 UI。此外,它不能使用自定义材质和着色器,并且不容易从 MonoBehaviours 中引用。

Unity’s goal is to replace the uGUI system entirely with the new UI Toolkit. However, it is still in development and does not have all the functionalities that uGUI does. For example, the UI Toolkit cannot make UI that is positioned in the 3D world, like what we discussed in Chapter 16. It can only make UI that overlays on top of the screen. Additionally, it cannot use custom materials and shaders and is not easily referenced from MonoBehaviours.

那么,您何时会想要使用 UI 工具包?如果您想创建风格化的 UI,而又不想处理预制件和预制件变体带来的臃肿问题,那么 UI 工具包将非常有用。您可以通过代码更改 UI 的设计,而不必更改 GameObject 的 Inspector 属性。这使得 UI 更容易在多个项目之间共享,并且可以快速自定义。此外,如果您有 Web 开发经验,您可能会更熟悉UI 工具包的工作流程。

So, when would you want to use the UI Toolkit? The UI Toolkit is extremely helpful if you want to create stylistic UI without dealing with the bloat that comes along with prefabs and prefab variants. You can change the design of UI through code instead of altering a GameObject’s Inspector properties. This makes UI more easily sharable across multiple projects and quickly customizable. Additionally, if you have experience with web development, you may be more comfortable with the workflow of the UI Toolkit.

如果您正在开发一个较旧的项目,则可能需要本书中讨论的有关 uGUI 的信息,因为它仍然是使用最广泛的 UI 系统。但是,如果您正在创建一个新项目并希望考虑使用 UI Toolkit 进行项目开发,我建议您您查看以下文档以确保此系统能够满足您的需求: https://docs.unity3d.com/Manual/UI-system-compare.xhtml

Odds are, if you are working on an older project, you will need the information discussed in this book that concentrates on uGUI as it is still the most widely used UI system. However, if you are creating a new project and would like to consider using the UI Toolkit for your project, I recommend you review the following documentation to ensure your needs will be met by this system: https://docs.unity3d.com/Manual/UI-system-compare.xhtml

现在我们已经了解了何时可以使用 UI 工具包,让我们开始将其安装到我们的项目中。

Now that we’ve reviewed when you can use the UI Toolkit, let’s get started with it by installing it in our project.

安装 UI Toolkit 包

Installing the UI Toolkit package

根据您使用的 Unity 版本,可能未安装必要的 UI Toolkit 包。版本所有相对较新的 Unity 版本都附带了可用于开发编辑器 UI 的 UI 工具包;但是,仅最新版本才附带允许您制作运行时 UI 的版本

Depending on which version of Unity you are using, the necessary UI Toolkit packages may not be installed. The version of the UI Toolkit that allows you to develop Editor UI comes packaged with all relatively recent versions of Unity; however, the version that allows you to also make runtime UI only comes with the most recent versions.

您可以通过转到窗口| UI Toolkit来确定是否已安装 UI Toolkit 包的所有必要版本。如果UI Builder子菜单不存在或 UI Toolkit 菜单项完全缺失,则需要导入该包。

You can determine if you already have all necessary versions of the UI Toolkit package installed by going to Window | UI Toolkit. If the UI Builder submenu is not present or the UI Toolkit menu item is completely missing, you will need to import the package.

图 18.2:确定您是否安装了 UI Toolkit 包

图 18.2:确定您是否安装了 UI Toolkit 包

Figure 18.2: Determining if you have the UI Toolkit package installed

如果需要安装 UI Toolkit,可以通过从git URL 导入包来安装。为此,请完成以下步骤:

If you need to install the UI Toolkit, you can do so by importing the package from the git URL. To do so, complete the following steps:

  1. 使用Window | Package Manager打开包管理器
  2. Open the package manager with Window | Package Manager.
  3. 选择+按钮,然后选择从git URL添加包…
  4. Select the + button, then choose Add package from git URL….
图 18.3:通过 git URL 添加包

图 18.3:通过 git URL 添加包

Figure 18.3: Adding a package via the git URL

  1. 在出现的文本框中输入com.unity.ui。确保地址后面没有空格。否则,您将收到错误消息。

    导入完成后,您应该在包窗口中看到以下工具包

  2. Type com.unity.ui into the text field that appears. Make sure not to add a space after the address. If you do, you will receive an error message.

    After the import finishes, you should see the following toolkit in your package window.

图 18.4:UI 工具包

图 18.4:UI 工具包

Figure 18.4: The UI Toolkit package

  1. 如果你想导入示例放入您的项目中,选择导入按钮进行下载。
  2. If you’d like to import the examples into your project, select the Import button to download them.
  3. 您可能会注意到,UI Toolkit 描述指出 UI Builder 不包含在该包中。因此,您必须单独导入该包。为此,请选择+按钮,然后再次选择从 git URL添加包…
  4. You may notice that the UI Toolkit description says that the UI Builder is not included in this package. So, you must import that package separately. To do so, select the + button, then choose Add package from git URL… again.
  5. 这次,在出现的文本框中输入com.unity.ui.builder

    导入完成后,您应该看到以下包。

  6. This time, enter com.unity.ui.builder into the textbox that appears.

    After the import finishes, you should see the following package.

图 18.5:UI Builder 包

图 18.5:UI Builder 包

Figure 18.5: The UI Builder package

  1. 与其他包一样,您可以使用导入按钮将示例导入到您的项目中
  2. As with the other package, there are samples you can import into your project with the Import buttons.

现在我们已经安装了适当的软件包,让我们看看 UI Toolkit 系统的各个部分。

Now that we have the appropriate packages installed, let’s look at the various parts of the UI Toolkit system.

UI Toolkit 系统的组成部分

Parts of the UI Toolkit system

UI 工具包使用一组资源和一个 GameObject 组件来创建 UI。用于创建 UI 的主要资源创建的UI如下:

The UI Toolkit uses a set of assets and a GameObject component to create UI. The primary assets used to create UI are as follows:

  • UI文档(UXML)
  • UI Document (UXML)
  • 样式表 (USS)
  • Style Sheet (USS)
  • 面板设置
  • Panel Settings
  • 主题样式表 (TSS)
  • Theme Style Sheet (TSS)
图 18.6:制作 UI Toolkit 界面时使用的资产

图 18.6:制作 UI Toolkit 界面时使用的资产

Figure 18.6: Assets used when making UI Toolkit interfaces

UI 文档是UXML 文件类型(扩展名为.uxml)。此文件使用Unity 可扩展标记语言( UXML ) 来定义UI 的布局和结构。虽然 UXML 是unity 特定标记语言,其工作方式与 HTML 和 XML 等其他标记语言类似。您可以通过右键单击 Asset 文件夹并选择Create | UI Toolkit | UI Document来创建 UXML 文件,然后为文件指定适当的名称。

A UI Document is a UXML file type (.uxml extension). This file uses Unity Extensible Markup Language (UXML) to define the layout and structure of the UI. While UXML is a unity-specific markup language, it works similarly to other markup languages like HTML and XML. You can create UXML files by right-clicking in an Asset folder and selecting Create | UI Toolkit | UI Document, then naming the file appropriately.

样式表是一种 USS 文件类型(.uss扩展名)。此文件用于指定可以在 UXML 文件中引用的样式属性。在使用 HTML 时,这与 CSS 文件非常相似。您可以通过右键单击 Asset 文件夹并选择Create | UI Toolkit | Style Sheet来创建 USS 文件,然后命名文件适当。

A Style Sheet is a USS file type (.uss extension). This file is used to designate style properties that can be referenced in a UXML file. This works very similarly to a CSS file when working with HTML. You can create USS files by right-clicking in an Asset folder and selecting Create | UI Toolkit | Style Sheet, then naming the file appropriately.

面板设置资源(.asset扩展名)定义UI所具有的属性的集合。

A Panel Settings asset (.asset extension) defines the collection of properties that the UI will have.

图 18.7:默认面板设置资产的属性

图 18.7:默认面板设置资产的属性

Figure 18.7: A default Panel Settings asset’s properties

主题样式表是TSS文件类型(.tss扩展名)。它决定了哪些面板设置将与哪些 TSS 一起使用,以及USS 文件,通过维护它们的集合,如下图所示。

A Theme Style Sheet is a TSS file type (.tss extension). It determines which Panel Settings will be used with which TSS and USS files, by maintaining a collection of them, as shown in the following figure.

图 18.8: TSS 文件的检查器

图 18.8: TSS 文件的检查器

Figure 18.8: The Inspector of a TSS file

当您创建面板时设置资产,一个名为Unity Themes的文件夹自动在同一文件夹中创建。在其中,将为您创建一个名为UnityDefaultRuntimeTheme.tss的 TSS 文件。但是,您可以通过右键单击 Asset 文件夹并选择创建| UI 工具包| TSS主题文件来创建更多 TSS 文件。

When you create a Panel Settings asset, a folder called Unity Themes is automatically created in the same folder. Within it, a single TSS file called UnityDefaultRuntimeTheme.tss will be created for you. However, you can create more TSS files by right-clicking in an Asset folder and selecting Create | UI Toolkit | TSS Theme File.

UI Toolkit 系统的最后一部分是UI Document组件,如下面的屏幕截图所示

The final piece of the UI Toolkit system is the UI Document component, shown in the following screenshot.

图 18.9:UI 文档组件

图 18.9:UI 文档组件

Figure 18.9: The UI Document component

此组件会添加到场景中的 GameObject 中,并允许渲染使用 UI Toolkit 创建的 UI。它有三个属性:面板设置源资产排序顺序

This component is added to a GameObject in your scene and allows your UI created with the UI Toolkit to be rendered. It has three properties: Panel Settings, Source Asset, and Sort Order.

您将面板设置资产分配给面板设置属性,以表示此组件渲染的 UI 将具有哪些设置。源资产属性是您分配 UI 文档 (UXML) 文件的地方。排序顺序属性决定了 UXML 文件定义的 UI 将以何种顺序呈现,相对于您在使用相同面板设置的场景。您可以使用Inspector中的Add Component并搜索UI Document将此组件添加到 GameObject 中

You assign a Panel Setting asset to the Panel Settings property to signify which settings the UI rendered by this component will have. The Source Asset property is where you assign the UI Document (UXML) file. The Sort Order property determines what order the UI defined by the UXML file will render relative to any other UXML files you are rendering in the scene that use the same Panel Settings. You can add this component to a GameObject by using Add Component in the Inspector and searching for UI Document.

现在我们知道了所有必需的资产和组件,让我们回顾一下如何使用UI Builder 实际构建这些资产。

Now that we know all the required assets and components, let’s review how to actually build these assets using the UI Builder.

视觉元素和 UI 层次结构

Visual Elements and UI Hierarchy

就像 uGUI 构建的 UI 由多个 GameObject 组成一样,UI Toolkit 构建的 UI 由Visual Elements组成。有UI Toolkit 系统中为您提供了多个 UI 元素(按钮、标签、滑块等),但 Visual Element 是所有 UI 元素的基类,因此它们的许多属性都从中派生出来。

In the same way that a uGUI-built UI is comprised of multiple GameObjects, a UI Toolkit-built UI is comprised of Visual Elements. There are multiple UI elements available to you in the UI Toolkit system (buttons, labels, sliders, etc.), but the Visual Element is the base class for all of them, so they all derive many of their properties from it.

正如游戏对象在 Unity 编辑器层级结构中组织一样,UI Toolkit 也以某种方式组织其视觉元素称为UI 层次结构。例如,假设我想创建一个类似于图 18 .10中显示的 UI 。

Just as GameObjects are organized in the Unity Editor Hierarchy, UI Toolkit organizes its Visual Elements in something called a UI Hierarchy. For example, let’s say I’d like to create a UI similar to the one displayed in Figure 18.10.

图 18.10:简单示例 UI

图 18.10:简单示例 UI

Figure 18.10: Simple sample UI

场景层次结构如果编辑器是用 uGUI 构建的,那么它与 UI Hierarchy 非常相似,如果它是用用UI Builder创建,如图18.11所示

The Scene Hierarchy in the Editor, if it were built with uGUI, is quite similar to the UI Hierarchy, if it were created with UI Builder, as shown in Figure 18.11.

图 18.11:uGUI 的场景层次结构与 UI Toolkit 的 UI 层次结构

图 18.11:uGUI 的场景层次结构与 UI Toolkit 的 UI 层次结构

Figure 18.11: Scene Hierarchy for uGUI versus UI Hierarchy for UI Toolkit

除了命名和嵌套方面略有不同外,它们几乎完全相同。您可以考虑 UI 文档,它在 UI 层次结构中以Chapter18.uxml表示,其作用类似于场景层次结构中的 Canvas 功能

Except for some slight changes in naming and nesting, they are almost the same. Where you can consider the UI Document, represented by Chapter18.uxml in the UI Hierarchy acting with a similar function to the Canvas in the Scene Hierarchy.

笔记

Note

UI 层次结构屏幕截图是从 UI Builder 工具获得的,我们将在下一节中讨论它。

The UI Hierarchy screenshot was obtained from the UI Builder tool, which we will discuss in the next section.

使用 uGUI 时,每个 GameObject 的对齐和位置都基于其嵌套的父对象。对于使用 UI Toolkit 系统创建的视觉元素也是如此。因此,了解项目应如何嵌套对于使用 UI Toolkit系统开发 UI 非常重要。

When working with uGUI, the alignment and position of each GameObject is based on the parent object it is nested under. This is true also for Visual Elements created with the UI Toolkit system. So, having a sense of how items should be nested is important for developing UI with the UI Toolkit system.

笔记

Note

您可以在代码包中提供的 Unity 项目中找到为图 18.1018.11开发的示例 UI 。它们可以在标记为Chapter18.asset的场景中找到。在场景层次结构中,Canvas GameObject 包含 uGUI 版本,而 UIDocument GameObject包含 UI Toolkit 版本。

You can find the example UI that was developed for Figure 18.10 and Figure 18.11 within the Unity project provided in the code bundle. They can be found within the scene labeled Chapter18.asset. In the Scene Hierarchy, the Canvas GameObject holds the uGUI version and the the UIDocument GameObject holds the UI Toolkit version.

现在我们已经回顾了 UI Toolkit 系统的一些基本概念,让我们看看如何实际使用它构建 UI。

Now that we’ve reviewed some basic concepts of the UI Toolkit system, let’s look at how we can actually build UI with it.

使用 UI Builder 创建 UI

Creating UI with the UI Builder

要使用 UI Toolkit 创建 UI,您需要必须创建一个 UI 文档来描述你将渲染哪些视觉元素以及它们的布局和其他属性。在 UI Toolkit 系统中创建 UI 文档的两种方法:

To create UI with the UI Toolkit, you have to create a UI Document to describe what Visual Elements you will render as well as their layout and other properties. There are two ways to create UI Documents within the UI Toolkit system:

  • 编写布局视觉元素的代码
  • Write the code that will lay out the Visual Elements
  • 将元素放在 UI Builder 中并让它为你编写代码
  • Lay the elements in UI Builder and have it write the code for you

您还可以将两者结合起来,在通过代码或UI Builder 编辑 UI 之间切换。

You can also do a combination of both and bounce between editing your UI via code or the UI Builder.

要访问 UI Builder,请选择窗口| UI 工具包| UI Builder

To access the UI Builder, select Window | UI Toolkit | UI Builder.

图 18.12:UI Builder 窗口

图 18.12:UI Builder 窗口

Figure 18.12: The UI Builder window

在此窗口中,您可以将各种视觉元素从库(左下角)拖放到视口(中间)。您可以调整大小和还可以在此视口中移动视觉元素。您可以更改父级,从而更改视觉元素的对齐方式通过层次结构(左中)。此外,您还可以在UXML 审阅面板中查看从布局生成的代码文件,并在USS 预览面板中查看代表样式的代码文件(均位于底部中央)。本章末尾的示例部分提供了一个使用 UI Builder 构建 UI 的示例。

In this window, you can drag and drop various Visual Elements from the Library (bottom left) into the Viewport (center). You can resize and move the Visual Elements around in this Viewport, as well. You can change the parenting, thus changing how the Visual Element is aligned via the Hierarchy (left center). Additionally, you can also view the code file that is generated from your layout in the UXML Review Panel and the code files that represent your styles in the USS Preview Panel (both are in the bottom center). An example of walking through how to build a UI with the UI Builder is in the Examples section at the end of this chapter.

为了能够使用通过 UI Builder 创建的 UI 布局,您必须将 UI 文档保存为 .uxml文件。按Ctrl + S会将文件保存在您选择的位置。我建议您将它们保存在Assets中名为UI Toolkit的文件夹中

To be able to use the UI layout you’ve created with the UI Builder, you must save the UI Document as a .uxml file. Pressing Ctrl + S will save the file for you in a location of your choosing. I recommend you save them in a folder called UI Toolkit within Assets.

现在我们已经回顾了如何创建 UI 文档,让我们回顾一下如何将文档与游戏场景联系起来。

Now that we’ve reviewed how to create a UI Document, let’s review how to tie the document to your game’s scene.

您可以通过双击层次结构中的任意视觉元素并输入新名称来重命名它们。使用 UI 构建器时,如果需要,视觉元素的名称将充当变量通过 C# 代码引用它们。因此,赋予视觉元素独特的如果您计划通过 C# 代码访问它们,则可以使用名称(有关更多信息,请参阅使 UI 可通过事件交互部分)。

You can rename any of your Visual Elements by double-clicking on them in the Hierarchy and typing a new name. When using the UI Builder, the names of the Visual Elements will act as variables if you need to reference them by C# code. So, it’s important to give the Visual Elements distinct names if you plan to access them via C# code (see the Making the UI Interactable with Events section for further information).

使用 UI 文档组件

Using the UI Document component

正如本章UI 工具包系统各部分所述,UI 文档组件必须添加到场景中的 GameObject 中,以便渲染您创建的 UI。您可以通过将 UI Document 组件添加到现有 GameObject 或选择+ | UI Toolkit | UI Document来执行此操作。此 GameObject 将具有Transform组件和UI Document组件。

As mentioned in the Parts of the UI Toolkit System section of this chapter, the UI Document component must be added to a GameObject in your scene so that the UI you’ve created can be rendered. You can do this by either adding the UI Document component to an existing GameObject or selecting + | UI Toolkit | UI Document. This GameObject will have a Transform component and a UI Document component.

图 18.13: UIDocument 游戏对象的检查器

图 18.13: UIDocument 游戏对象的检查器

Figure 18.13: The Inspector of a UIDocument GameObject

如果您的项目Assets文件夹中还没有名为UI Toolkit的文件夹,系统将自动为您创建一个。在其中,您将找到一个PanelSettings资产和一个名为UnityThemes的文件夹。在UnityThemes中,您将找到一个名为UnityDefaultRuntimeTheme.tss的 TSS 文件。您的UIDocument GameObject 的UI Document组件将自动将PanelSettings资产分配给Panel Settings属性。但是,您需要将 UI Document 挂接到Source Asset属性中。将您所需的任何.umxl文件拖放到创建到Source Asset属性中,以查看您在场景中创建的 UI 。

If you do not already have a folder called UI Toolkit within your projects’s Assets folder, one will automatically be created for you. Within it, you will find a PanelSettings asset, and a folder called UnityThemes. Within UnityThemes, you will find a TSS file called UnityDefaultRuntimeTheme.tss. Your UIDocument GameObject’s UI Document component will automatically have the PanelSettings asset assigned to the Panel Settings property. However, you will need to hook your UI Document into the Source Asset property. Drag and drop whatever .umxl file you created into the Source Asset property to view the UI you created in your scene.

现在我们已经了解了构建和渲染 UI 的基础知识,让我们看看如何编写使用UI 工具包创建的 UI 交互。

Now that we know the basics of building and rendering our UI, let’s look at how to code the interactions of UI created with the UI Toolkit.

使 UI 可与 C# 交互

Making The UI interactable with C#

UI Builder 和你的UXML文档仅处理您的 UI。虽然您可以通过使用样式表分配一些基本响应(例如悬停时更改视觉效果),但您需要创建 C# 脚本来处理与您的 UI 相关的任何逻辑或事件。

The UI Builder and your UXML document only handle the visual properties of your UI. While you can assign some basic responses (like changing visuals on hover) through the use of style sheets, you will need to create C# scripts to handle any logic or events related to your UI.

UIElements 命名空间

The UIElements namespaces

能够编写与 UI 交互的代码文档,您必须使用UnityEngine.UIElements命名空间。如果您使用 UI Toolkit 制作编辑器UI,您可能还需要UnityEditor.UIElements命名空间。

To be able to write code that interfaces with UI Documents, you must use the UnityEngine.UIElements namespace. If you are using the UI Toolkit to make editor UI, you may also need the UnityEditor.UIElements namespace.

笔记

Note

有关UnityEngine.UIElementsUnityEditor.UIElements命名空间的更多信息,请参阅以下资源:https ://docs.unity3d.com/Packages/com.unity.ui@1.0/api/UnityEditor.UIElements.xhtml 。

For more information about the UnityEngine.UIElements and UnityEditor.UIElements namespaces, see the following resource: https://docs.unity3d.com/Packages/com.unity.ui@1.0/api/UnityEditor.UIElements.xhtml.

获取对 UI Documents 变量的引用

Getting a reference to UI Documents variables

在您的 C# 脚本中,您可以创建一个UIDocument类型的引用变量。您可以像分配其他类的变量一样分配此变量:在Inspector中分​​配它、使用GetComponent、从另一个脚本传递对它的引用等等

Within your C# script, you can create a reference variable to a UIDocument type. You can assign this variable in the same way you would generally assign a variable of another class: assigning it in the Inspector, using GetComponent, passing a reference to it from another script, and so on.

笔记

Note

虽然您可以使用FindObjectOfType ,但不建议用它来查找对UIDocument 的引用,因为您的场景中很可能有多个 UI 文档

While you can use FindObjectOfType, this is not recommended for finding a reference to a UIDocument, as you will very likely have multiple UI Documents in your scene.

在获得要使用的UIDocument的引用后,您可以获得 Visual通过获取对 UI 文档上的根可视化元素的引用,然后查询该元素中的可视化元素,来获取其中的元素(例如按钮、标签等)按其名称输入。

After you get a reference to the UIDocument you want to work with, you can get a reference to the Visual Elements within it (e.g., the buttons, labels, etc.) by getting a reference to the root Visual Element on the UI Document, then Querying that element for the Visual Element type by its name.

例如,在本章前面展示的 UI 中,我将Label重命名为DogLabel,将Button重命名为CatButton,如下图所示

For example, in the UI shown earlier in the chapter, I have renamed the Label to DogLabel and the Button to CatButton, as shown in the following figure:

图 18.14:标签和按钮的新名称

图 18.14:标签和按钮的新名称

Figure 18.14: New names for Label and Button

为了在 C# 脚本中创建对这些变量的引用,我首先使用以下代码为UIDocumentLabelButton创建了引用变量

To create a reference to these variables in a C# script, I first created reference variables for the UIDocument, the Label, and the Button, with the following code:

[SerializeField] 私有 UIDocument uiDocument;
自有品牌 dogLabel;
私人按钮 catButton;
[SerializeField] private UIDocument uiDocument;
private Label dogLabel;
private Button catButton;

uiDocument变量将在 Inspector 中分​​配。与所有MonoBehaviour脚本一样,此脚本需要附加到场景中的 GameObject。我将此脚本附加到UIDocument GameObject,然后将UIDocument GameObject 拖到Ui Document属性中

The uiDocument variable will be assigned in the Inspector. As with all MonoBehaviour scripts, this script needed to be attached to a GameObject in the scene. I attached this script to the UIDocument GameObject and then dragged the UIDocument GameObject into the Ui Document property.

图 18.15:将 UIDocument 分配给脚本

图 18.15:将 UIDocument 分配给脚本

Figure 18.15: Assigning the UIDocument to the script

值得注意的是,当您在 C# 中引用UIDocument ,则引用的是UIDocument组件,而不是 UI Document源文件。

It’s important to note that when you reference a UIDocument in C#, you are referencing the UIDocument component, not a UI Document source file.

为了初始化dogLabelcatButton ,我获取了对uiDocument上的rootVisualElement的引用,然后使用Query方法查找每个Visual Element 。

To initialize dogLabel and catButton, I got a reference to the rootVisualElement on the uiDocument, then used the Query method to find each Visual Element.

无效开始(){
      var root = uiDocument.rootVisualElement;
      dogLabel = root.Q<Label>("DogLabel");
      catButton = root.Query<Button>("CatButton");
}
void Start(){
      var root = uiDocument.rootVisualElement;
      dogLabel = root.Q<Label>("DogLabel");
      catButton = root.Query<Button>("CatButton");
}

注意我如何使用root.Q来查找Label并使用root.Query来查找Button 。您可以使用其中任何一种,我这样做只是为了让您看到有两种不同的方法可以做到这一点。使用哪一种取决于您的偏好。

Notice how I used root.Q to find the Label and root.Query to find the Button. You can use either one, and I only did this so you can see that there are two different ways to do it. It is up to your preference which one you use.

管理视觉元素事件

Managing Visual Element events

由于UXML文档不管理事件,因此您可以了解UXML文档中定义的UI是否与通过订阅事件或在事件触发时注册回调方法。

Since the UXML document does not manage events, you can find out if the UI defined in the UXML document is interacted with by either subscribing to an event or registering a callback method when the event is triggered.

例如,如果我想在单击catButton时在控制台中记录一条消息,我需要创建一个订阅 catButton单击事件的方法。

For example, if I want to log a message in the Console when the catButton is clicked, I need to create a method that is subscribed to the catButton’s clicked event.

首先,我在脚本中添加以下方法:

First, I add the following method to my script:

私有 void OnCatButtonClicked() {
     Debug.Log("CatButtonClicked");
}
private void OnCatButtonClicked() {
     Debug.Log("CatButtonClicked");
}

然后,我需要订阅catButton点击事件,在Start()方法中使用以下内容

Then, I need to subscribe to the catButton’s clicked event, with the following in the Start() method.

catButton.clicked += OnCatButtonClicked;
catButton.clicked += OnCatButtonClicked;

为了避免在 GameObject 被禁用或销毁时事件订阅出现任何错误,我使用OnDisable()方法取消订阅该事件。

To avoid any errors with the event subscription when the GameObject is disabled or destroyed, I unsubscribe from the event in the OnDisable() method.

私有无效OnDisable(){
     catButton.clicked-=OnCatButtonClicked;
}
private void OnDisable() {
     catButton.clicked -= OnCatButtonClicked;
}

这样,每当您单击按钮时,控制台中就会显示OnCatButtonClicked消息。

This causes the OnCatButtonClicked message to display in the Console whenever you click the button.

访问视觉元素属性

Accessing Visual Element properties

类似于 uGUI GameObjects 的方式它们的属性可以通过 C# 代码更改,Visual Element 的属性也可以通过 C# 代码调整。例如,将OnCatButtonClicked()方法更改为以下内容将使单词Text更改Meow!

Similar to the way uGUI GameObjects can have their properties changed via C# code, a Visual Element can have its properties adjusted via C# code, as well. For example, changing the OnCatButtonClicked() method to the following will make the word Text change to Meow!

公共相机theCamera;
无效更新()
{
    变换。查看(2 * 变换。位置 - 相机。变换。位置);
}
public Camera theCamera;
void Update()
{
    transform.LookAt(2 * transform.position - theCamera.transform.position);
}

上述代码使 UI 显示如下:

The preceding code causes the UI to appear as follows:

图 18.16:点击按钮后文本发生改变的 UI

图 18.16:点击按钮后文本发生改变的 UI

Figure 18.16: The UI after the text changes via a button click

我还有很多可以说很多关于如何使用 UI Toolkit 的内容,但正如我在本章开头所说的那样,我只能给出一个总体概述——否则,我就得单独写一本关于 UI Toolkit 的书了!不过,我相信我已经对重要概念进行了足够的概述,以便您可以深入研究下一节中的示例

There is a lot more I could say about working with the UI Toolkit, but as I said at the beginning of this chapter, I could only give a general overview—otherwise, I’d have to write a whole separate book just on the UI Toolkit! However, I believe I’ve given enough of an overview of the important concepts so that you can dive into working with the examples in the next section.

示例

Examples

现在我们已经掌握了基本构建块,我们需要开始使用 UI Toolkit 实际创建一些 UI。我没有深入研究样式表或各种视觉元素的属性,但我将在这里展示一些示例来扩展这些内容。如果在完成这些示例后,您想了解更多信息,请参阅资源部分,获取大量示例和文档。

Now that we have the basic building blocks we need to get started with actually creating some UI with UI Toolkit. I didn’t dive deep into style sheets or the properties of the various Visual Elements, but I’ll show some examples that will expand upon that here. If after completing these examples, you want to learn more, see the Resources section for a bunch of examples and documentation.

使用 UI 工具包开发UI,主要有两个部分的工作:

When using the UI Toolkit to develop UI, there are two main parts to the work:

  • 使用 UI Builder 布局 UI(或通过编辑UXML 文档)
  • Laying out the UI with the UI Builder (or by editing the UXML document)
  • 编写 C# 代码以向UI添加功能
  • Writing C# code to add functionality to the UI

因此,每个示例将分为两部分:一部分用于构建 UI,另一部分用于编写功能。

So, each of these examples will be broken into two parts: one for building the UI and the other for writing the functionality.

由于 UI 工具包可以用于编辑器和运行时 UI,我将向您展示两者的一个示例。让我们从编辑器示例开始!

Because the UI Toolkit can be used for both Editor and runtime UI, I’ll show you one example of both. Let’s start with the Editor example!

使用 UI 工具包制作编辑器虚拟宠物

Using the UI Toolkit to make an Editor virtual pet

虽然本书主要关注运行时 UI,但我没有向您展示如何使用 UI Toolkit 制作一些编辑器 UI,因为这是它的主要优点之一。编辑器 UI 通常用于工具来促进开发、提高生产力、简化工作流程等等。最初,我计划向您展示如何制作一些通用的编辑器工具,您可以扩展这些工具来改善您的工作流程。然而,我决定制作一个可以改善您心情的编辑器工具。毕竟,自我照顾对生产力至关重要!虽然这个例子在传统意义上可能没有任何实际用途,但它很有趣,它向您展示了如何使用许多编辑器 UI 功能,所以这是一个双赢的结果。

While the primary focus of this book is on runtime UI, I would be remiss to show you how to use the UI Toolkit to make some Editor UI, since that’s one of its main benefits. Editor UI is usually used for tools to facilitate development, improve productivity, streamline your workflow, and so on. Originally, I planned to show you how to make some generic Editor tools that you could expand to improve your workflow. However, I decided instead to make an Editor tool that can improve your mood. Self-care is essential to productivity, after all! While this example might not have any practical purposes in the traditional sense, it’s fun and it shows you how to use lots of Editor UI features, so it’s a win-win.

本示例介绍如何制作一个虚拟宠物,它会在编辑器窗口中陪伴您,并在您点击它时向您致意。本示例还演示了如何在Unity 编辑器中创建动画精灵表。

This example covers how to make a virtual pet that hangs out with you in a window in your Editor and compliments you when you click on it. This also demonstrates how to create animated sprite sheets within the Unity Editor.

图 18.17:本例中创建的编辑器虚拟宠物

图 18.17:本例中创建的编辑器虚拟宠物

Figure 18.17: The Editor virtual pet created in this example

本例中使用的猫精灵来自此处提供的公共领域艺术资产: https: //opengameart.org/content/a-cat

The cat sprite used in this example is derived from the public domain art asset provided here: https://opengameart.org/content/a-cat.

笔记

Note

对于此示例,由于我们正在制作一个与我们迄今为止所做的任何工作都不相关的编辑器工具,因此您可能需要创建一个新的 Unity 项目来完成这项工作。如果这样做,您将需要重新导入任何 2D UI 包。

For this example, since we are making an Editor Tool that is not related to any of the work we’ve done so far, you might want to create a new Unity project to complete this work in. If you do, you will need to reimport any 2D UI packages.

让我们从布局开始使用 UI Builder 来创建 UI

Let’s start with laying out the UI with the UI Builder!

使用 UI Builder 制作编辑器窗口的 UI

Using the UI Builder to make our Editor Window’s UI

要创建如图 18.17所示的虚拟宠物,请完成以下步骤:

To create the virtual pet shown in Figure 18.17, complete the following steps:

  1. 在为您的编写代码时Editor,您应该将所有编辑器代码放在Editor文件夹中。这向 Unity 表明其中的任何代码都只能用于 Editor,而不能在运行时使用。在Assets中创建一个名为Editor的文件夹,并在其中创建一个名为Resource 的文件夹。
  2. When writing code for your Editor, you should put all your editor code within an Editor folder. This signifies to Unity that any code within it is only to be used for the Editor and not at runtime. Create a folder in Assets called Editor and a folder within it called Resource.
  3. 将书的源文件中找到的idleCat.png精灵表拖到新创建的Assets/Editor/Resources文件夹中。
  4. Drag the idleCat.png sprite sheet found in the book’s source files into your newly created Assets/Editor/Resources folder.
  5. 设置其导入设置,使其纹理类型Sprite (2D 和 UI)Sprite 模式 Multiple
  6. Set its Import Settings so that its Texture Type is Sprite (2D and UI) and Sprite Mode is Multiple.
  7. 打开Sprite 编辑器,你会发现图像非常模糊。我们需要进一步编辑导入设置来解决这个问题。
  8. Open the Sprite Editor and you’ll notice that the images are extremely blurry. We need to edit the import settings further to fix this.
图 18.18:模糊的精灵表

图 18.18:模糊的精灵表

Figure 18.18: The blurry sprite sheet

  1. 返回精灵的导入设置,在默认设置下,将最大尺寸设置为64压缩设置。同时,将过滤模式改为点(无过滤)
  2. Return to the sprite’s Import Settings, and under the Default settings, set the Max Size to 64 and Compression to None. Also, change the Filter Mode to Point (no filter).
  3. 选择“应用”,精灵现在应该是清晰的像素艺术猫。
  4. Select Apply and the sprites should now be crisp pixel art cats.
  5. 现在,返回Sprite Editor并选择Slice。将TypeGrid By Cell Size更改为 Pixel Size ,并将Pixel Size设置为16 x 16
  6. Now, return to the Sprite Editor and select Slice. Change the Type or Grid By Cell Size and set the Pixel Size to 16 x 16.
图 18.19:切片精灵表

图 18.19:切片精灵表

Figure 18.19: The sliced sprite sheet

  1. 选择切片按钮确认更改,然后选择“应用”以提交更改。现在您可以关闭Sprite 编辑器
  2. Select the Slice button to confirm the changes, then select Apply to commit the changes. You can now close the Sprite Editor.
  3. 现在我们已经正确导入了精灵,我们准备在 UI Builder 中构建编辑器 UI。使用Window | UI Toolkit | UI Builder打开 UI Builder 。
  4. Now that we have our sprite imported properly, we’re ready to build out the Editor UI in the UI Builder. Open the UI Builder with Window | UI Toolkit | UI Builder.
  5. 在层次结构中选择<未保存的文件>.uxml UI 文档以在检查中调出其属性
  6. Select the <unsaved file>.uxml UI Document in the Hierarchy to bring up its properties in the Inspector.
图 18.20:UXML 文件的检查器

图 18.20:UXML 文件的检查器

Figure 18.20: The UXML file’s Inspector

  1. 现在将画布大小更改为100 x 100
  2. Now change Canvas Size to 100 x 100.
  3. 通过以下方式保存 UI 文档视口中选择文件|保存。将文件保存在Assets/Editor/Resources文件夹中,并将文件命名为IdleCat.uxml.uxml扩展名会自动添加)。
  4. Save the UI Document by selecting File | Save from within the Viewport. Save the file in the Assets/Editor/Resources folder and name the file IdleCat.uxml (the .uxml extension is automatically added for you).
  5. 现在,让我们将猫添加到Viewport中。因为我们想让猫可点击,所以最简单的方法是使用按钮。将图标从控制面板拖到Viewport中。注意,一旦这样做,按钮将位于容器的顶部。
  6. Now, let’s add the cat to the Viewport. Because we want to make the cat clickable, the easiest way to achieve this is to use a Button. Drag the icon from the Controls Panel to the Viewport. Note once you do so the Button will be positioned at the top of the container.
图 18.21:添加按钮

图 18.21:添加按钮

Figure 18.21: Adding a Button

  1. 层次结构中选择按钮以打开其检查器。
  2. Select the Button in the Hierarchy to open its Inspector.
  3. 展开背景属性这样您就可以改变按钮的外观。
  4. Expand the Background property so that you can change the look of the button.
  5. 从图像属性旁边的下拉菜单中选择Sprite。这将允许您选择Sprite类型的图像。
  6. Select Sprite from the dropdown menu next to the Image property. This will allow you to select images of Sprite type.
图 18.22:将背景图像类型更改为 Sprite

图 18.22:将背景图像类型更改为 Sprite

Figure 18.22: Changing the Background Image type to Sprite

  1. 在Image属性字段中选择圆圈来搜索精灵,然后选择idleCat_0 。执行此操作后,您应该在视口中看到以下内容
  2. Select the circle in the Image property field to search for sprites and select idleCat_0. You should see the following in your Viewport once you do so:
图 18.23:应用了猫图像的按钮

图 18.23:应用了猫图像的按钮

Figure 18.23: The Button with the cat image applied

  1. 让我们把这段文字发出来的方法。在Inspector的顶部,在Button属性下,从Text属性中删除单词Button
  2. Let’s get that text out of the way. At the top of the Inspector, under the Button properties, delete the word Button from the Text property.
图 18.24:从按钮中删除文本

图 18.24:从按钮中删除文本

Figure 18.24: Removing the Text from the button

  1. 现在,让我们把它变成适当的尺寸。展开Size属性并将Sizeauto x auto更改为64 x 64
  2. Now, let’s make it the appropriate dimensions. Expand the Size property and change the Size from auto x auto to 64 x 64.
图 18.25:更改按钮大小

图 18.25:更改按钮大小

Figure 18.25: Changing the button size

  1. 视觉元素始终在左上角的 UI 文档根容器中初始化。如果您希望能够将它们放置在容器内,则必须创建一个高级视觉元素,作为所有其他项目可以放置在其中的“外壳”。因此,要使此按钮在窗口中居中,我们需要添加一个视觉元素来包含它。将VisualElement库拖到层次结构
  2. Visual Elements always initialize in the UI Document root container at the top-left corner. If you want to be able to position them within the container, you have to create a high-level Visual Element that acts as the “shell” in which all the other items can be positioned within. So, to make this Button centered within the window, we will need to add a Visual Element to contain it. Drag VisualElement from the Library into the Hierarchy.
图 18.26:向层次结构添加视觉元素

图 18.26:向层次结构添加视觉元素

Figure 18.26: Adding a Visual Element to the Hierarchy

  1. 层次结构中,将按钮拖放到VisualElement上。这将使Button成为 VisualElement 的元素
  2. Within the Hierarchy, drag and drop the Button onto the VisualElement. This will cause the Button to be a child of the VisualElement.
图 18.27:使按钮成为 VisualElement 的子元素

图 18.27:使按钮成为 VisualElement 的子元素

Figure 18.27: Making the Button a child of VisualElement

  1. 层次结构中选择VisualElement以打开检查器
  2. Select VisualElement from the Hierarchy to open its Inspector.
  3. 您可能会注意到 VisualElement并未完全适合根容器。展开Flex属性并将Grow属性更改为1。这将导致VisualElement填充整个根容器。
  4. You may notice that the VisualElement does not fully fit within the root container. Expand the Flex property and change the Grow property to 1. This will cause the VisualElement to fill the whole root container.
图 18.28:更新 Grow 属性

图 18.28:更新 Grow 属性

Figure 18.28: Updating the Grow property

  1. 现在,让我们将猫放在VisualElement的中心。展开VisualElementAlign属性,并将Align ItemsJustify Content属性都置于中心
  2. Now, let’s center the cat within the VisualElement. Expand the Align property of the VisualElement and center for both the Align Items and Justify Content properties.
图 18.29:调整 VisualElement 的 Align 属性

图 18.29:调整 VisualElement 的 Align 属性

Figure 18.29: Adjusting the Align properties of the VisualElement

  1. 让我们改变VisualElement的背景颜色。在Background属性中,更改通过选择颜色块并在十六进制属性中输入BE8D8D ,将颜色更改为BE8D8D
  2. Let’s change the background color of the VisualElement. In the Background property, change the color to BE8D8D by selecting the color block and typing BE8D8D into the Hexidecimal property.
图 18.30:调整 VisualElement 的背景颜色

图 18.30:调整 VisualElement 的背景颜色

Figure 18.30: Adjusting the VisualElement’s background color

  1. 现在,很容易看到我们的猫按钮周围有一些我们不想要的背景和边框。要删除它们,请选择按钮更改其背景颜色,使 alpha0
  2. Now, it’s easy to see there’s some background and border around our cat Button that we don’t want. To remove this, select the Button and change its background color so that the alpha is 0.
图 18.31:删除按钮的背景颜色

图 18.31:删除按钮的背景颜色

Figure 18.31: Removing the Button’s background color

  1. Button周围仍然有我们不想要的边框。展开Border属性并将颜色的 alpha 更改为0。现在您应该有以下内容:
  2. There is still a border around the Button that we don’t want. Expand the Border property and change the color’s alpha to 0. You should now have the following:
图 18.32:最终的 UI 外观

图 18.32:最终的 UI 外观

Figure 18.32: The final UI look

  1. 我们几乎完成了 UI Builder。我们需要做的最后一件事是重命名Button,以便我们可以轻松地在 C# 代码中访问它。双击层次结构中的Button并将其重命名为CatButton
  2. We’re almost done with the UI Builder. The last thing we need to do is rename Button so that it will be easily accessible in our C# code. Double-click on Button in the Hierarchy and rename it CatButton.
  3. Ctrl + S保存您的工作,我们就完成了!您应该有以下内容:
  4. Save your work by pressing Ctrl + S and we’re done! You should have the following:
图 18.33:具有适当名称的 IdleCat.uxml 层次结构

图 18.33:具有适当名称的 IdleCat.uxml 层次结构

Figure 18.33: The IdleCat.uxml Hierarchy with appropriate names

笔记

Note

如果将UIDocument GameObject 添加到当前打开的场景中,您可能会看到有关控制台中缺少面板设置的警告。您可以删除此 GameObject 并关闭错误消息。

If a UIDocument GameObject was added to your currently open scene, you may see a warning about a Panel Setting missing in your Console. You can just delete this GameObject and dismiss the error message.

现在我们的 UI 已经完全布局好了,我们可以编写代码来开始添加功能!

Now that our UI is fully laid out, we can write the code to start adding in the functionality!

编写 C# 代码来创建编辑器窗口并添加功能

Writing C# code to make our Editor Window and add functionality

让我们的猫出现在我们的Unity Editor 并具有功能,我们需要编写一些 C# Editor 代码。我们希望猫能做以下几件事:

To get our cat to appear as a window in our Unity Editor and have functionality, we need to write some C# Editor code. There are a few things we want the cat to do:

  • 当您使用工具菜单或使用预定义热键时出现在浮动窗口中
  • Appear in a floating window when you use a Tools menu or use pre-defined hotkeys
  • 将鼠标悬停在它上面时发出“喵”的叫声
  • Say “meow” when you hover over it
  • 单击控制台时会称赞您
  • Compliment you in the Console when you click on it
  • 坐着并播放循环播放的空闲动画
  • Sit with a looping idle animation
  • 当你点击它时播放站立动画(即抚摸它)
  • Play a standing animation when you click on it (aka pet it)

我们可以用一个 C# 脚本实现所有这些。要让您的虚拟宠物出现在 Unity 编辑器的窗口中,请完成以下步骤:

We can achieve all of this in one C# script. To have your virtual pet appear in a window within your Unity Editor, complete the following steps:

  1. 在编辑器文件夹中,创建一个名为IdleCat.cs的 C# 脚本并打开它。
  2. Within your Editor folder, create a C# script called IdleCat.cs and open it.
  3. 我们需要做的第一件事是将脚本从 MonoBehaviour 更改EditorWindow 将类定义更改为以下内容:
    公共类 IdleCat:EditorWindow {

    这将自动添加UnityEditor命名空间。

  4. The first thing we need to do is change our script from a MonoBehaviour to an EditorWindow. Change the class definition to the following:
    public class IdleCat : EditorWindow {

    This will automatically add the UnityEditor namespace.

  5. 通过在 Unity 的菜单栏中选择“工具” | “我很孤独”,窗口就会出现。如果我们输入Ctrl + Shift + Kk代表“kitty”) ,它也会打开。为此,请在IdleCat.cs脚本中写入以下代码:
    [MenuItem("工具/我很孤独_%#K")]
     公共静态void ShowIdleCat(){
         EditorWindow 窗口 = GetWindow<IdleCat>();
         窗口.titleContent = 新的GUIContent (“Kitty”);
    }

    在上述代码中,[MenuItem("Tools/I'm Lonely _%#K")]行创建Tools | I'm Lonely菜单项,并在选择该菜单项时运行ShowIdleCat()方法。_ %#K表示也可以使用Ctrl + Shift + K快捷键来实现此目的

    以下行实例化窗口并将其标题设置Kitty

    EditorWindow 窗口 = GetWindow<IdleCat>();
    窗口.titleContent = 新的GUIContent (“Kitty”);

    保存你的代码,然后你应该会在编辑器中看到菜单项。单击它或键入Ctrl + Shift + K应该会打开一个名为Kitty 的窗口。

  6. We will have the window appear by selecting Tools | I’m Lonely from the Menu Bar within Unity. It will also open if we type Ctrl + Shift + K (k for “kitty”). To achieve this, write the following code in your IdleCat.cs script:
    [MenuItem("Tools/I'm Lonely _%#K")]
     public static void ShowIdleCat() {
         EditorWindow window = GetWindow<IdleCat>();
         window.titleContent = new GUIContent("Kitty");
    }

    In the preceding code, the [MenuItem("Tools/I'm Lonely _%#K")] line creates the Tools | I’m Lonely menu item and runs the ShowIdleCat() method when it is selected. _%#K signifies that this can also be achieved with the Ctrl + Shift + K shortcut.

    The following lines instantiate the window and set its title to Kitty:

    EditorWindow window = GetWindow<IdleCat>();
    window.titleContent = new GUIContent("Kitty");

    Save your code and you should see the menu item in your Editor. Clicking it or typing Ctrl + Shift + K should bring up a window named Kitty.

图 18.34:工具​​菜单和 Kitty 窗口

图 18.34:工具​​菜单和 Kitty 窗口

Figure 18.34: The Tools menu and the Kitty window

  1. 你的窗户可能与我的尺寸不同。别担心。让我们通过代码设置尺寸。将以下两行代码添加到ShowIdleCat()方法中:
    窗口.maxSize = 新 Vector2(100,100);
    窗口.最小尺寸 = 窗口.最大尺寸;

    这不会自动更改当前打开的窗口的大小,因为此功能仅在您使用菜单项或快捷键时运行。现在,您可以通过执行其中一个操作来调整窗口大小。您无需关闭窗口。您可以在窗口仍打开时执行此操作。

    需要注意的一点是,编辑器窗口可以停靠在整个 Unity 编辑器中。因此,窗口的大小可以根据用户停靠的位置而变化。

  2. Your window may be a different size than mine. Don’t worry. Let’s set the size via code. Add the following two lines of code to your ShowIdleCat() method:
    window.maxSize = new Vector2(100, 100);
    window.minSize = window.maxSize;

    This will not automatically change the size of your currently open window, because this function only runs when you use the menu item or the shortcut keys. You can now resize your window by doing one of those actions now. You do not need to close the window. You can do this while it is still open.

    One thing to note is that Editor Windows can be docked throughout the Unity Editor. So, the size of your window can change based on where the user docks it.

  3. 现在,我们需要参考我们在此脚本中使用 UI Builder 创建的 UI 文档。由于这是一个编辑器脚本,因此无法将其附加到游戏对象。这意味着我们无法通过拖放或使用GetComponent来分配变量。相反,我们必须从Editor/Resources文件夹中加载它。

    使用编辑器 UI 时,CreateGUI()方法是用于初始化UI 的事件函数。

    编写以下代码,确保将UnityEngine.UIElements命名空间添加到脚本顶部:

    私有无效CreateGUI(){
         var 根 = rootVisualElement;
         var quickToolVisualTree = Resources.Load<VisualTreeAsset>("IdleCat");
         quickToolVisualTree.CloneTree(根);
    }

    此代码块执行了几项操作。首先,它获取我们创建的编辑器窗口的rootVisualElement 。然后,它通过在Editor/Resources文件夹中搜索名为IdleCat的VisualTreeAsset来找到IdleCat.uxml文件。然后,它克隆IdleCat.uxml UI 文档并将其放置在窗口的根目录中。

    如果您返回编辑器,您现在应该可以在打开的窗口的粉红色背景上看到您的小猫。

  4. Now, we need to reference the UI Document we created with the UI Builder in this script. Since this is an Editor script, it can’t be attached to a GameObject. That means we can’t assign the variable via drag and drop or using GetComponent. Instead, we will have to load it via from our Editor/Resources folder.

    When working with Editor UI, the CreateGUI() method is an Event Function used to initialize the UI.

    Write the following code, making sure to add the UnityEngine.UIElements namespace to the top of the script:

    private void CreateGUI() {
         var root = rootVisualElement;
         var quickToolVisualTree = Resources.Load<VisualTreeAsset>("IdleCat");
         quickToolVisualTree.CloneTree(root);
    }

    This code block does a few things. First, it gets the rootVisualElement of the Editor Window we have created. Then, it finds the IdleCat.uxml file by searching for a VisualTreeAsset named IdleCat within the Editor/Resources folder. Then, it clones the IdleCat.uxml UI Document and places it within the window’s root.

    If you return to the Editor, you should now see your Kitty on the pink background in your open window.

图 18.35:具有适当大小和 UI 文档的窗口

图 18.35:具有适当大小和 UI 文档的窗口

Figure 18.35: The window with the appropriate size and UI Document

  1. 现在,我们需要访问按钮。将以下变量声明添加到您的脚本:
    私人按钮 catButton;
  2. Now, we need to get access to the button. Add the following variable declaration to your script:
    private Button catButton;
  3. 要分配catButton,我们需要查询对象。将以下行添加到CreateGUI()方法中:
    catButton = root.Q<Button>("CatButton");
  4. To assign the catButton, we need to Query the root object. Add the following line to your CreateGUI() method:
    catButton = root.Q<Button>("CatButton");
  5. 接下来,让我们在鼠标悬停在按钮上时显示工具提示。将以下代码行添加到CreateGUI()方法中:
    catButton.tooltip = "喵";

    保存脚本后,您应该能够立即在编辑器中看到这些更改。(请注意,我的屏幕截图工具不会捕获鼠标光标,因此下图中未显示。)

  6. Next, let’s have the tooltip appear when we hover over the button. Add the following line of code to the CreateGUI() method:
    catButton.tooltip = "meow";

    After you save the script, you should be able to immediately see these changes reflected in your Editor. (Note that my screenshot tool doesn’t capture the mouse cursor, so it is not shown in the following figure.)

图 18.36: 喵喵提示

图 18.36: 喵喵提示

Figure 18.36: The meow tooltip

  1. 现在,当您点击猫时,我们可以让猫称赞您。首先,让我们创建一个名为OnCatButtonClicked()的新方法,该方法在控制台中显示“您做得很棒!”消息。将以下代码添加到您的脚本中:
    私有无效OnCatButtonClicked()
    {
        Debug.Log("你做得很好!");
    }
  2. Now, we can have the cat compliment you when you click on it. First, let’s create a new method called OnCatButtonClicked() that shows the "You're doing great!" message in the Console. Add the following code to your script:
    private void OnCatButtonClicked()
    {
        Debug.Log("You're doing great!");
    }
  3. 我们需要让OnCatButtonClicked()方法订阅和取消订阅点击catButton上的事件。将以下行添加到CreateGUI()方法中:
    catButton.clicked += OnCatButtonClicked;

    另外,创建以下OnDisable()方法:

    私有无效OnDisable()
    {
        catButton.clicked-=OnCatButtonClicked;
    }

    一旦保存,当您单击它时,您就可以在控制台中看到猫对您表示称赞。

  4. We need to have the OnCatButtonClicked() method subscribe and unsubscribe to the click event on catButton. Add the following line to your CreateGUI() method:
    catButton.clicked += OnCatButtonClicked;

    Also, create the following OnDisable() method:

    private void OnDisable()
    {
        catButton.clicked -= OnCatButtonClicked;
    }

    Once you save, you can already see the cat compliment you in the Console when you click on it.

图 18.37:赞美的猫

图 18.37:赞美的猫

Figure 18.37: The complimenting cat

  1. 我们的虚拟宠物的最后两个目标是让它动起来。这需要一点努力。我们不能使用 Unity 动画来实现它,因为我们在编辑器中。所以,我们必须编写代码它将通过某种计时器上的代码将catButton上的背景与适当的图像交换

    实现这一点的一种方法是使用协程,但是不幸的是,协程在编辑器中默认不起作用。但是,我们可以通过从Unity 导入编辑器协程包来在我们的编辑器中使用协程。

    为此,请选择“窗口” | “包管理器”以打开包管理器

  2. Our last two goals for our virtual pet involve animating it. This takes a bit of effort. We can’t use a Unity animation to achieve it because we’re in the Editor. So, we have to write code that will swap out the background on our catButton with the appropriate image via code on some kind of timer.

    One way to do this is with coroutines, but unfortunately, coroutines do not work by default in the Editor. However, we can use coroutines in our Editor by importing the Editor Coroutines package from Unity.

    To do that, select Window | Package Manager to open the Package Manager.

  3. 从下拉菜单中选择“Package: Unity Registry”来查看所有可用的Unity 包。
  4. Select Package: Unity Registry from the dropdown to view all available Unity packages.
图 18.38:更改包管理器中显示的包

图 18.38:更改包管理器中显示的包

Figure 18.38: Changing which packages appear in the Package Manager

  1. 现在,滚动直到看到Editor Coroutines。它可能被锁定了。如果是,请选择Unlock
  2. Now, scroll until you see Editor Coroutines. It will likely be locked. If it is, select Unlock.
图 18.39:解锁编辑器协程包

图 18.39:解锁编辑器协程包

Figure 18.39: Unlocking the Editor Coroutines package

  1. 现在你已经解锁编辑器协同程序包中,您可以将以下命名空间添加到IdleCat.cs脚本的顶部
    使用 Unity.EditorCoroutines.Editor;

    现在,我们可以在代码中使用EditorCoroutines了!它们的工作原理与常规协程非常相似。但是,在编写第一个 Editor 协程来控制动画之前,让我们先访问我们想要在动画中使用的图像并编写一些辅助函数。

  2. Now that you’ve unlocked the Editor Coroutines package, you can add the following namespace to the top of your IdleCat.cs script:
    using Unity.EditorCoroutines.Editor;

    Now, we can use EditorCoroutines in our code! They work pretty similarly to regular coroutines. But, before we write our first Editor coroutine to control our animations, let’s get access to the images we’ll want to use in our animation and write some helper functions.

  3. 我希望猫有两个动画:一个空闲动画和一个抚摸动画(鼠标单击将触发)。我将把这些动画的图像存储在两个单独的列表中。要将它们用作背景图像,我需要将它们存储在StyleBackgrounds列表中。将以下内容添加到您的类中:
    私有 List<StyleBackground> idleBackgrounds = new List<StyleBackground>();
    私有列表 <StyleBackground> pettingBackgrounds = 新列表 <StyleBackground>();
  4. I want the cat to have two animations: an idle animation and a petting animation (which will be triggered by the mouse click). I’ll store the images for these animations in two separate Lists. To use them as background images, I’ll need to store them in lists of StyleBackgrounds. Add the following to your class:
    private List<StyleBackground> idleBackgrounds = new List<StyleBackground>();
    private List<StyleBackground> pettingBackgrounds = new List<StyleBackground>();
  5. 在将适当的精灵存储在这些列表中之前,我们需要从精灵表中获取对所有精灵的引用。我们将以类似于之前找到 UI 文档文件的方式执行此操作。将以下代码行添加到您的CreateGUI()方法中:
    Sprite[] allSprites = Resources.LoadAll<Sprite>("idleCat");

    这将查找Editor/Resources文件夹并将所有名为 idleCat 的精灵存储allSprites数组中。

  6. Before we can store the appropriate sprites in those Lists, we need to get a reference to all of the sprites from the sprite sheet. We’ll do this similarly to how we found the UI Document file earlier. Add the following line of code to your CreateGUI() method:
    Sprite[] allSprites = Resources.LoadAll<Sprite>("idleCat");

    This will look in the Editor/Resources folder and store all of the sprites with the name idleCat to the allSprites array.

  7. 以下框架将表示具体的动画:
    图 18.40: 哪些精灵进入哪个动画

    图 18.40: 哪些精灵进入哪个动画

    现在,我们需要循环遍历allSprites数组并将这些精灵分配到正确的列表中。我们可以通过在CreateGUI()方法中添加以下代码来实现

    对于(int i = 0; i <= allSprites.Length - 1; i++)
    {
        StyleBackground backgroundImage = new StyleBackground(allSprites[i]);
        如果 (i < 11)
        {
            抚摸背景.添加(背景图像);
        }
        如果 (i >= 10)
        {
            空闲背景.添加(背景图像);
        }
    }
  8. The following frames will represent the specific animations:

    Figure 18.40: Which sprites go to which animation

    Now, we need to loop through the allSprites array and divvy those sprites up into the correct list. We can do so by adding the following code to the CreateGUI() method:

    for (int i = 0; i <= allSprites.Length - 1; i++)
    {
        StyleBackground backgroundImage = new StyleBackground(allSprites[i]);
        if (i < 11)
        {
            pettingBackgrounds.Add(backgroundImage);
        }
        if (i >= 10)
        {
            idleBackgrounds.Add(backgroundImage);
        }
    }
  1. 在我们编写将背景替换为正确图像的方法之前,我们需要添加一个变量来跟踪按钮当前正在显示哪个动画的哪一帧。将以下变量初始化添加到您的类中:
    私有 int 动画索引 = 0;
  2. Before we can write the methods that swap out the background with the correct image, we need to add a variable that will keep track of which frame of which animation the button is currently displaying. Add the following variable initialization to your class:
    private int animationIndex = 0;
  3. 好的,现在我们需要编写方法以增加animationIndex并将catButtonbackgroundImage设置为适当的精灵。将以下方法添加到您的类中以控制空闲动画的动画:
    私有无效空闲动画()
    {
        动画索引++;
        如果(动画索引 >= idleBackgrounds.Count)
        {
            动画索引 = 0;
        }
        
        catButton.style.backgroundImage = 空闲背景[动画索引];
    }

    请注意,当索引超出范围时,它会循环回到0

  4. OK, now we need to write methods that will increase the animationIndex and set the catButton’s backgroundImage to the appropriate sprite. Add the following method to your class to control the animation for the idle animation:
    private void IdleAnimation()
    {
        animationIndex++;
        if (animationIndex >= idleBackgrounds.Count)
        {
            animationIndex = 0;
        }
        
        catButton.style.backgroundImage = idleBackgrounds[animationIndex];
    }

    Notice that when the index is out of range, it loops back around to 0.

  5. 添加类似的方法来控制抚摸动画:
    私有无效PettingAnimation()
    {
        动画索引++;
        如果(animationIndex> = pettingBackgrounds.Count)
        {
            动画索引 = 0;
        }
        
        catButton.style.backgroundImage = pettingBackgrounds[animationIndex];
    }
  6. Add a similar method to control the petting animation:
    private void PettingAnimation()
    {
        animationIndex++;
        if (animationIndex >= pettingBackgrounds.Count)
        {
            animationIndex = 0;
        }
        
        catButton.style.backgroundImage = pettingBackgrounds[animationIndex];
    }

笔记

Note

IdleAnimation ()PettingAnimation()方法有一些可重用的代码,可以重构以简洁;但是,为了清楚起见,我将保持原样

The IdleAnimation() and PettingAnimation() methods have some reusable code and could be refactored for brevity; however, for clarity, I will keep it the way it is.

  1. 现在,我们准备使用EditorCortoutine来调用这些方法并播放动画。让我们首先创建以下Editor 协程:
    IEnumerator NextAnimationFrame()
    {
        var waitForOneSecond = new EditorWaitForSeconds(1f);
        
        while(真)
        {
            产生返回 waitForOneSecond;
            空闲动画();
        }
    }

    这将创建一个无限循环,每秒运行一次IdleAnimation()方法。

  2. Now, we’re ready to use an EditorCortoutine to call these methods and get the animations playing. Let’s start by creating the following Editor coroutine:
    IEnumerator NextAnimationFrame()
    {
        var waitForOneSecond = new EditorWaitForSeconds(1f);
        
        while (true)
        {
            yield return waitForOneSecond;
            IdleAnimation();
        }
    }

    This will create an infinite loop that runs the IdleAnimation() method every second.

  3. 我们需要启动上一步中编写的 Editor 协程。将以下代码添加到CreateGUI()方法中:
    EditorCoroutineUtility.StartCoroutine(NextAnimationFrame(),this);
  4. We need to start the Editor coroutine that we wrote in the previous step. Add the following code to your CreateGUI() method:
    EditorCoroutineUtility.StartCoroutine(NextAnimationFrame(), this);
  5. 如果你返回编辑器,你应该能够看到猫在循环播放它的空闲动画。现在,我们需要编写一些代码,以便在您单击猫时播放抚摸动画。为此,我们需要另一个变量。将以下内容添加到您的类中:
    私人 bool 空闲 = true;

    这个变量将跟踪猫是否应该空转。

  6. If you return to your Editor, you should be able to see the cat flicking its tail while it loops through its idle animation. Now, we need to write some code that will make the petting animation play when you click on the cat. To do this, we need another variable. Add the following to your class:
    private bool idle = true;

    This variable will keep track of whether or not the cat should be idling.

  7. 将以下内容添加到OnCatButtonClicked()方法中,以表示一旦单击猫,它就不再处于空闲状态:
    空闲=假;
  8. Add the following to the OnCatButtonClicked() method to signify cat should no longer be idle once it is clicked:
    idle = false;
  9. 更新NextAnimationFrame() Editor 协程,使其根据idle变量在空闲和抚摸动画之间切换。粗体代码是新代码:
    IEnumerator NextAnimationFrame()
    {
        var waitForOneSecond = new EditorWaitForSeconds(1f);
        while(真)
        {
            产生返回 waitForOneSecond;
            如果(空闲)
            {
                空闲动画();
            }
            别的
            {
                抚摸动画();
            }
        }
    }
  10. Update the NextAnimationFrame() Editor coroutine so that it switches between the idle and petting animations based on the idle variable. The code in bold is new:
    IEnumerator NextAnimationFrame()
    {
        var waitForOneSecond = new EditorWaitForSeconds(1f);
        while (true)
        {
            yield return waitForOneSecond;
            if (idle)
            {
                IdleAnimation();
            }
            else
            {
                PettingAnimation();
            }
        }
    }
  11. 目前,如果您单击猫,它将进入抚摸动画,但它会无限期地循环播放该动画。我们需要它在抚摸动画完成后返回空闲动画。更新PettingAnimation()方法以在if语句中包含idle = true 。当动画完成时,这会将idle变量重置为true 。
  12. Currently, if you click on the cat, it will enter the petting animation, but it will keep looping through that animation indefinitely. We need it to go back to the idle animation once the petting animation completes. Update the PettingAnimation() method to include idle = true inside the if statement. This will reset the idle variable to true when the animation is complete.
  13. 我们已经正式完成了所有计划,但我们的代码有一个问题。如果您将Debug.Log放入协程的while循环中,然后关闭Kitty窗口,您会看到Debug.Log一直在控制台中打印!我们需要在窗口关闭时停止该协程的while循环。为此,请向您的类添加另一个变量:
    私有 bool windowOpen = true;
  14. We’ve officially completed everything we set out to do, but there is one problem with our code. If you put a Debug.Log in the coroutine’s while loop and then close the Kitty window, you’ll see that the Debug.Log keeps printing in the Console! We need to stop that coroutine’s while loop when the window closes. To do that, add another variable to your class:
    private bool windowOpen = true;
  15. 将NextAnimationFrame()协程中的while循环更改为以下内容:
    while (窗口打开) {
  16. Change the while loop within the NextAnimationFrame() coroutine to the following:
    while (windowOpen) {
  17. 现在,我们只需要在窗口关闭时将windowOpen设置为false 。因此,将以下内容添加到OnDisable()方法中:
    窗口打开=false;
  18. Now, we just need to set windowOpen to false when the window closes. So, add the following to the OnDisable() method:
    windowOpen = false;

就是这样!现在你应该有一只小猫咪朋友,在你工作时陪你玩。每当你需要一点提神剂时,点击她。

That’s it! You should now have a little kitty friend that hangs out with you while you work. Click on her whenever you need a little pick-me-up.

使用 UI Toolkit 制作带有样式表和动画过渡的菜单

Using the UI Toolkit to make a menu with style sheets and animation transitions

我们将了解如何使用 UI Builder创建一个使用样式表和过渡动画的基本菜单,然后我们将连接菜单中的按钮来访问网络数据并在此示例中随机生成内容。

We’ll look at how to use the UI Builder to create a basic menu that uses style sheets and transition animations, then we’ll hook up buttons within the menu to access web data and randomly generate content in this example.

由于上一个示例是一只旨在为您带来快乐的虚拟宠物,因此我决定在本示例中也坚持“自我照顾”的主题。下图是我们将要制作的 UI 的屏幕截图使用随机生成的猫图像和从网络上检索到的引语。

Since the last example was a virtual pet meant to bring you happiness, I decided to stick with the theme of “self-care” for this example, as well. The following figure is a screenshot of the UI we will make with a randomly generated image of a cat and a quote retrieved from the web.

图 18.41:具有随机生成内容的 UI 菜单

图 18.41:具有随机生成内容的 UI 菜单

Figure 18.41: The UI menu with randomly generated content

按下Charm Me按钮将随机生成一张猫图片,按下Inspire Me按钮将随机生成一句励志名言。此外,当鼠标悬停在按钮上并点击按钮时,按钮会改变颜色,同时动画显示为稍大的形状。

Pressing the Charm Me button will randomly generate a cat picture and pressing the Inspire Me button will randomly generate an inspirational quote. Additionally, the buttons change color when hovered over and clicked on while animating to a slightly bigger shape.

与前面的示例一样,我们将首先使用 UI Builder 布置 UI

As with the previous example, we will start by laying out the UI with the UI Builder.

使用 UI Builder 布局菜单、制作样式表和制作动画过渡

Using the UI Builder to lay out a menu, make style sheets, and make animation transitions

要创建如图 18.41所示的 UI ,请完成以下步骤:

To create the UI shown in Figure 18.41, complete the following steps:

  1. 创建新的名为Chapter18-Examples 的场景
  2. Create a new scene called Chapter18-Examples.
  3. 在场景层次结构中单击鼠标右键,然后选择“UI Toolkit” | “UI Document”

    如果你还没有有一个,这个将自动创建一个名为UI Toolkit的新Assets文件夹。它将包含一个PanelSettings资源和一个Unity Themes文件夹。

  4. Right-click in the Scene Hierarchy and select UI Toolkit | UI Document.

    If you don’t already have one, this will automatically create a new Assets folder called UI Toolkit. It will contain a PanelSettings asset and a Unity Themes folder.

  5. UI Toolkit文件夹中,右键单击并选择Create | UI Toolkit | UI Document。将其命名为InspirationalMenu.uxml
  6. Within the UI Toolkit folder, right-click and select Create | UI Toolkit | UI Document. Name it InspirationalMenu.uxml.
  7. 将新的 UXML 文件拖到UIDocument GameObject上的UI Document组件的Source Asset属性中
  8. Drag the new UXML file into the Source Asset property of the UI Document component on the UIDocument GameObject.
  9. 为了使我们具有相同的游戏视图分辨率,请将游戏场景视图设置840x630
  10. Just so that we have the same Game view resolution, set your Game scene view to 840x630.
图 18.42:设置游戏视图分辨率

图 18.42:设置游戏视图分辨率

Figure 18.42: Setting your Game view resolution

  1. 双击InspirationalMenu.uxml文件以打开UI 生成器。
  2. Double-click on the InspirationalMenu.uxml file to open the UI Builder.
  3. 层次结构中选择InspirationalMenu.uxml以查看检查器
  4. Select InspirationalMenu.uxml from the Hierarchy to view its Inspector.
  5. 改变将Canvas Size设置为与Game View 匹配。这将导致Viewport中的容器成为与你的游戏视图大小相同
  6. Change the Canvas Size to Match Game View. This will cause the container in the Viewport to be the same size as your Game view.
图 18.43:设置画布大小以匹配游戏视图

图 18.43:设置画布大小以匹配游戏视图

Figure 18.43: Setting the Canvas Size to Match Game View

  1. 控件窗口拖拽一个按钮进入视口。它应该延伸到整个容器。
  2. Drag a Button from the Controls window into the Viewport. It should stretch all the way across the container.
  3. 按钮应该按钮周围会出现蓝色轮廓,右下角会出现一个手柄。选择该手柄可将按钮大小调整为令人愉悦的“按钮式”尺寸。具体尺寸由您决定
  4. The Button should have a blue outline around it and a handle in its bottom-right corner. Select that handle to resize the button to a pleasing “button-like” size. It’s up to you what size.
图 18.44:在视口中调整大小的按钮元素

图 18.44:在视口中调整大小的按钮元素

Figure 18.44: The Button element resized in the Viewport

  1. 通过调整Text属性将Button上的文本更改为Charm Me
  2. Change the text on Button to Charm Me by adjusting the Text property.
图 18.45:如何更改按钮上显示的文本

图 18.45:如何更改按钮上显示的文本

Figure 18.45: How to change the text that displays on the button

  1. 展开Text属性并将字体样式更改为粗体大小更改为22,并将对齐方式更改为垂直和水平居中。将句柄根据需要将按钮放大或缩小一点,以适合新的文本大小。
  2. Expand the Text property and change the Font Style to Bold, the Size to 22, and the Alignment to center vertically and horizontally. Drag the handle of the button to make it a bit bigger or smaller as needed to fit the new text size.
图 18.46:按钮的文本设置

图 18.46:按钮的文本设置

Figure 18.46: The Text settings of Button

  1. 展开背景属性并将颜色设置 FF9BC8
  2. Expand the Background property and set the Color to FF9BC8.
图 18.47:更改按钮的背景颜色

图 18.47:更改按钮的背景颜色

Figure 18.47: Changing the background color of the button

  1. 展开Border属性并将颜色设置为白色,宽度5,并且半径设为 10
  2. Expand the Border property and set the Color to white, the Width to 5, and the Radius to 10.
图 18.48:调整边框属性

图 18.48:调整边框属性

Figure 18.48: Adjusting the Border properties

  1. 如果您进入游戏视图,您会注意到颜色与 UI 构建器中的颜色并不完全相同
    图 18.49:UI Builder Viewport 和 Editor Game View 之间的区别

    图 18.49:UI Builder Viewport 和 Editor Game View 之间的区别

    为了确保什么你看到的是UI Builder 与您的游戏视图匹配,从 UI Builder右上角的下拉菜单中选择Unity 默认运行时主题视口。

    图 18.50:将视口设置为 Unity 默认运行时主题

    图 18.50:将视口设置为 Unity 默认运行时主题

  2. If you go to your Game view, you’ll notice that the colors aren’t exactly the same as they are in the UI Builder.

    Figure 18.49: The difference between the UI Builder Viewport and Editor Game View

    To make sure what you are seeing in the UI Builder matches your Game view, select Unity Default Runtime Theme from the dropdown in the top-right corner of the UI Builder Viewport.

    Figure 18.50: Setting the Viewport to Unity Default Runtime Theme

  1. 将按钮文本 颜色更改为白色。
  2. Change the color of the Button’s Text to white.
图 18.51:更改文本颜色

图 18.51:更改文本颜色

Figure 18.51: Changing the Text color

  1. 现在,让我们创建一个面板将容纳按钮。我们将创建第二个按钮。将VisualElement容器 拖到
  2. Now, let’s create a Panel that will hold the Button. We will create the second button momentarily. Drag a VisualElement from the Containers Library to the Viewport.
  3. 按照与调整按钮大小相同的方式,将ViusalElement大小调整为合理的面板大小。
  4. In the same way that you resized the Button, resize the ViusalElement to a reasonable Panel size.
图 18.52:调整 VisualElement 的大小

图 18.52:调整 VisualElement 的大小

Figure 18.52: Resizing the VisualElement

  1. 设置背景VisualElement 的颜色FFEFEF
  2. Set the background color of the VisualElement to FFEFEF.
  3. 将边框颜色设置为白色的宽度5半径6。您应该在视口中看到以下内容:
  4. Set the Border color to white with a Width of 5 and a Radius of 6. You should see the following in your Viewport:
图 18.53:面板及其新设置

图 18.53:面板及其新设置

Figure 18.53: The Panel with its new settings

  1. 现在,让我们使按钮成为子按钮VisualElement面板。在层次结构中拖放并将按钮 拖放到VisualElement
  2. Now, let’s make the Button a child of the VisualElement Panel. In the Hierarchy drag and drop Button onto VisualElement.
图 18.54:将按钮设置为 VisualElement 的父级

图 18.54:将按钮设置为 VisualElement 的父级

Figure 18.54: Parenting the Button to the VisualElement

  1. 我想要两个面板的各个部分:左侧部分包含按钮,右侧部分包含猫的图像以及鼓舞人心的引用。添加一个VisualElement作为代表面板的VisualElement的子元素。您会注意到它会自动堆叠在视口中按钮下方。
  2. I want to have two sections of the Panel: the left section that holds the buttons and the right section that holds the image of the cat and the inspirational quote. Add a VisualElement as a child of the VisualElement that represents the Panel. You’ll notice that it automatically stacks under the Button in the Viewport.
图 18.55:添加新的 VisualElement

图 18.55:添加新的 VisualElement

Figure 18.55: Adding in a new VisualElement

  1. 使按钮成为VisualElement
  2. Make the Button a child of the new VisualElement.
  3. 现在,调整VisualElement 的大小,以便那它单击并拖动蓝色手柄,占据面板左侧的一部分。您应该看到如下所示的内容:
  4. Now, resize the VisualElement so that it takes up a portion of the left side of the Panel by clicking and dragging its blue handle. You should have something that looks like the following:
图 18.56:调整 VisualElement 的大小

图 18.56:调整 VisualElement 的大小

Figure 18.56: Resizing the VisualElement

  1. 现在,添加另一个VisualElement作为的孩子最高级别的VisualElement。您将看到类似以下内容:
  2. Now, add another VisualElement as a child of the highest-level VisualElement. You will see something like the following:
图 18.57:向层次结构添加另一个 VisualElement

图 18.57:向层次结构添加另一个 VisualElement

Figure 18.57: Adding another VisualElement to the Hierarchy

  1. 选择最顶部的VisualElement,并将Flex Direction更改为 row。你可以这样做选择 Flex 按钮VisualElement的顶部或 督察。
    图 18.58:将 Flex Direction 设置为 row

    图 18.58:将 Flex Direction 设置为 row

    这样做会导致它的两个VisualElement子元素从左到右排列,而不是从上到下排列

  2. Select the top-most VisualElement and change the Flex Direction to row. You can do this by either selecting the Flex button on the top of the VisualElement or in the Inspector.

    Figure 18.58: Setting the Flex Direction to row

    Doing this will cause its two VisualElement children to line up left to right rather than top to bottom.

  1. 从层次结构中选择最底层的VisualElement。在其Flex属性中检查器,将Grow属性更改为1。这将导致VisualElement填满其父级内的可用空间。
  2. Select the bottom-most VisualElement from the Hierarchy. In its Flex property within the Inspector, change the Grow property to 1. This will cause the VisualElement to fill up the available space within its parent.
  3. 现在,我们想让面板位于屏幕中央。为此,我们需要创建另一个VisualElement来容纳它,以便我们可以将其置于其父级的中心。将另一个VisualElement添加到层次结构中。
  4. Now, we want to make the Panel centered on the screen. To do this, we need to create another VisualElement that will hold it so that we can center it within its parent. Add another VisualElement to the Hierarchy.
  5. 将代表 Panel 的VisualElement拖到Hierarchy 中,使其成为新VisualElement的子元素。下图中,Hierarchy 中红色矩形框出的元素即代表 Panel 及其子元素的元素
  6. Drag the VisualElement that represents the Panel in the Hierarchy so that it is a child of the new VisualElement. In the following figure, the elements encircled by the red rectangle in the Hierarchy are the elements that represent the Panel and its children.
图 18.59:当前布局的层次结构

图 18.59:当前布局的层次结构

Figure 18.59: The Hierarchy of our current layout

  1. 选择层次结构中最顶层的VisualElement。您将看到它没有填满整个根容器。将其Flex Grow属性设置为1,以便它将填满可用空间。
  2. Select the top-most VisualElement in the Hierarchy. You’ll see it doesn’t fill the entire root container. Set its Flex Grow property to 1 so it will fill the available space.
  3. 展开对齐属性,并为对齐项目对齐内容属性选择居中。专家组应该现在位于容器的中心。
  4. Expand the Align property and select center for both the Align Items and Justify Content properties. The Panel should now be centered in the container.
  5. 在最顶部的VisualElement仍处于选中状态的情况下,通过扩展来更改背景图像Background属性,将Image类型改为Sprite,并将pinkBackground赋给Image 。此时你应该看到以下内容
  6. With the top-most VisualElement still selected, change the background image by expanding the Background property, changing the Image type to Sprite, and assigning pinkBackground to the Image. You should see the following at this point:
图 18.60:在背景上设置背景图像

图 18.60:在背景上设置背景图像

Figure 18.60: Setting the background image on the background

  1. 让我们将第二个按钮添加到面板中。将一个按钮控件库拖到层次结构中,以便它与另一个按钮成为兄弟。您应该看到类似下列的:
  2. Let’s add the second button to the Panel. Drag a Button from the Controls Library to the Hierarchy so that it is a sibling to the other Button. You should see something like the following:
图 18.61:添加新按钮

图 18.61:添加新按钮

Figure 18.61: Adding a new Button

  1. 我们可以使用样式表应用,而不必手动设置新按钮的所有属性以匹配其他按钮两者。这将确保它们始终具有相同的属性。选择第一个按钮(具有我们想要的属性的按钮)并在其Inspector中扩展其StyleSheet属性

    我们可以通过在文本框中输入名称,然后选择“将内联样式提取到新类”直接从其属性中创建样式表。

    在文本框中输入button-class,然后按将内联样式提取到新按钮。

  2. Instead of manually setting all the properties of the new Button to match the other, we can use a style sheet applied to both. This will make sure they both always have the same properties. Select the first Button (the one with the properties we want) and expand its StyleSheet property in its Inspector.

    We can create a style sheet directly from its properties by putting a name within the textbox and then selecting Extract Inlined Styles to New Class.

    Type button-class into the textbox and then press the Extract Inlined Styles to New Class button.

图 18.62:使用“提取内联样式到新类”创建样式表

图 18.62:使用“提取内联样式到新类”创建样式表

Figure 18.62: Creating a style sheet with Extract Inlined Styles to New Class

  1. 从中选择添加到新 USS出现的弹出窗口。
  2. Select Add to New USS from the popup that appears.
  3. 将文件在Asset/UI Toolkit中保存 ButtonStyle.uss
  4. Save the file in Asset/UI Toolkit as ButtonStyle.uss.
  5. ButtonStyle.uss样式​工作表应出现在现在样式表面
    图 18.63:新的样式表

    图 18.63:新的样式表

    现在,您可以将此样式应用于未设置样式的按钮,方法是将ButtonStyle.ussStyleSheet窗口拖到Button上。现在,这两个按钮将具有相同的属性。

    图 18.64:两个具有相同样式表的按钮

    图 18.64:两个具有相同样式表的按钮

  6. The ButtonStyle.uss style sheet should appear in the StyleSheets Panel now.

    Figure 18.63: The new style sheet

    You can now apply this style to the unstyled button by dragging ButtonStyle.uss from the StyleSheet window onto the Button. Now, the two Buttons will have the same properties.

    Figure 18.64: The two buttons with the same style sheet

  1. 更改文本底部按钮激发我的灵感
  2. Change the text on the bottom Button to Inspire Me.
  3. 现在让我们对齐这些按钮位于其VisualElement父级中。选择其VIsualElement父级并将其Align ItemsJustify Content属性都设置为居中。您可以可以从视口 (Viewport) 中VisualElement顶部的蓝色框或从检查器 (Inspector)中的Align属性执行此操作

    您的 UI Builder 应如下所示

  4. Now, let’s align these buttons in their VisualElement parent. Select their VIsualElement parent and set its Align Items and Justify Content properties both to center. You can do this either from the blue box on top of the VisualElement in the Viewport or from the Align property in the Inspector.

    Your UI Builder should look like the following:

图 18.65: 按钮位于其 VisualElement 父级的中心

图 18.65: 按钮位于其 VisualElement 父级的中心

Figure 18.65: The Buttons centered in their VisualElement parent

  1. 现在,让我们添加猫图片支架和将从互联网生成的引文。添加一个VisualElement,使其成为最底部VisualElement的子元素。
  2. Now, let’s add in the holders for the cat pictures and the quotes that will be generated from the internet. Add a VisualElement so that it is a child of the bottom-most VisualElement.
  3. 调整其大小,使其小于其VisualElement父级。
  4. Resize it so that it is smaller than its VisualElement parent.
  5. 展开Position属性在 Inspector 中将位置从Relative改为Absolute。这样你就可以通过输入数字或将其拖到 Viewport 中的位置来手动定位它。像这样定位它:
  6. Expand the Position property on its Inspector and change the position from Relative to Absolute. This will allow you to position it manually by either entering the numbers or dragging it into position in the Viewport. Position it like so:
图 18.66:手动定位 VIsualElement

图 18.66:手动定位 VIsualElement

Figure 18.66: Manually positioning the VIsualElement

  1. 添加标签作为同级标签我们刚才VisualElement创建。
  2. Add a Label as a sibling of the VisualElement we just created.
  3. 将其Position属性更改为Absolute,然后按如下方式缩放和定位
  4. Change its Position property to Absolute, then scale and position it as follows.
图 18.67:向 UI 添加标签

图 18.67:向 UI 添加标签

Figure 18.67: Adding a Label to the UI

  1. Text属性改为下图所示,字体颜色FF9BC8
  2. Change the Text properties to what is shown in the following figure. The font color is FF9BC8.
图 18.68:标签的 Text 属性

图 18.68:标签的 Text 属性

Figure 18.68: The Text property of the Label

  1. 删除文本,使其没有任何内容显示在那里。
  2. Delete the text so that there is nothing displayed there.
  3. 目前,当您点击按钮,没有反应太大。这让用户很难判断他们是否在点击按钮。所以,让我们给按钮赋予悬停和活动状态。
  4. Currently, when you click on the buttons, there is not much of a reaction. This makes it difficult for the user to tell if they are clicking the buttons. So, let’s give the buttons hover and active states.
  5. 样式表面板中选择.button-class。右键点击它并选择复制
  6. Select the .button-class in the StyleSheets Panel. Right-click on it and select Duplicate.
  7. 右键单击重复项并将其重命名为.button-class:hover。此命名约定表示此样式将应用于按钮的悬停状态。
  8. Right-click on the duplicate and rename it to .button-class:hover. This naming convention indicates that this style will be applied to the button’s hover state.
  9. 再次复制它并将新的副本重命名为.button-class:active。此命名约定表示此样式将应用于按钮的点击状态。
  10. Duplicate it again and rename the new duplicate to .button-class:active. This naming convention indicates that this style will be applied to the button’s click state.
  11. 选择.button-class:hover样式。在 Inspector 中,将背景颜色更改为E96FA6,并将其大小更改为200 x 90
  12. Select the .button-class:hover style. In the Inspector, change the Background color to E96FA6 and change its size to 200 x 90.
  13. 选择.button-class:active样式。在 Inspector 中,将背景颜色更改为C35D8B,然后将其尺寸更改为200 x 90
  14. Select the .button-class:active style. In the Inspector, change the Background color to C35D8B and change its size to 200 x 90.
  15. 您现在可以预览更改您可以通过以下方式进行您的游戏或使用视口 预览。这将向您显示悬停和活动状态的变化。
    图 18.69:使用视口中的预览查看按钮样式的变化

    图 18.69:使用视口中的预览查看按钮样式的变化

    当您将鼠标悬停并单击按钮时,按钮会变得更暗更大。

  16. You can now preview the changes you made by either playing your game or by using the Viewport Preview. This will show you the hover and active state changes.

    Figure 18.69: Using Preview in the Viewport to see the Buttons style changes

    The buttons now get darker and larger when you hover and click on them.

  1. 按钮上的这些样式变化很好,但从标准状态到其他状态的过渡有点激烈。我们可以为.button-class样式添加过渡动画,这样当它过渡到其他样式时,动画就会更流畅。

    StyleSheet面板中选择.button-class样式。在其检查器中,展开Transition Animation属性。我们希望它在缩放时进行动画处理。因此,使用下拉菜单将Propertyall更改为width 。此外,将Duration设置为0.5,将Easing设置为EaseIn。这表示当按钮改变宽度时,它将在 0.5 秒内完成并慢慢进入。

  2. These style changes on the buttons are fine, but the transition from its standard state to the others is a bit drastic. We can add transition animations to the .button-class style, so when it transitions to the other styles, it will animate more smoothly.

    Select the .button-class style from the StyleSheet Panel. Within its Inspector, expand the Transition Animation property. We want it to animate when it scales. So, change the Property from all to width using the dropdown. Also, set the Duration to 0.5 and the Easing to EaseIn. This indicates that when the button changes width, it will do so over 0.5 seconds and will ease into it.

图 18.70: 宽度过渡动画

图 18.70: 宽度过渡动画

Figure 18.70: The width transition animation

  1. 选择+ 添加过渡按钮并改变将新过渡的属性改为height0.5EaseIn。您的过渡动画现在应如下所示。这将确保按钮的宽度和高度发生变化时,它会在0.5 秒内平稳地完成。
    图 18.71:按钮的过渡动画

    图 18.71:按钮的过渡动画

    播放预览来观察鼠标悬停在按钮上时按钮逐渐变大的情况。

  2. Select the + Add Transition button and change the properties on the new transition to height, 0.5, and EaseIn. Your Transition Animations should now look like the following. This will ensure that when the width and height of the buttons change, it will do so smoothly over 0.5 seconds.

    Figure 18.71: The Buttons’ transition animations

    Play the Preview to watch the buttons grow gradually as you hover over them.

  1. 我们要做的最后一件事UI Builder 为每个视觉元素提供变量名称这样我们就可以通过代码找到它们。将各个可视化元素的名称更改为以下内容:
  2. The last thing we need to do in the UI Builder is give variable names for each of these Visual Elements so that we can find them via code. Change the names of the various Visual Elements to the following:
图 18.72:重命名我们想要通过代码访问的视觉元素

图 18.72:重命名我们想要通过代码访问的视觉元素

Figure 18.72: Renaming the Visual Elements we’ll want to access via code

呼。步骤好多啊!但是我们正式完成了 UI Builder。我们可以现在致力于我们的 C# 脚本来获取我们想要的功能

Whew. That was a lot of steps! But we are officially done with the UI Builder. We can now work on our C# scripts to get the functionality we want.

使用 C# 代码通过 Web 数据设置 VisualElement 和 Label 属性

Using C# code to set VisualElement and Label properties with web data

目前,我们的按钮不执行任何操作但当我们将鼠标悬停在它们上面或单击它们时,它们会动起来。我们现在需要将它们与某些功能联系起来。我们对这两个按钮单击时的目标如下

Currently, our Buttons do not do anything but animate when we hover over them or click them. We now need to hook them up to some functionality. Our goals for the two Buttons when clicked are as follows:

  • CharmButton :这应该从互联网上获取可爱的猫图片,并用可爱的猫图片替换CatPic视觉元素的背景
  • CharmButton: This should get cute cat pictures from the internet and replace the background of the CatPic Visual Element with the cute cat picture.
  • InspireButton:这应该从网络上获取励志名言,格式化文本,并将其放在InspirationalQuote标签中。
  • InspireButton: This should get inspirational quotes from the web, format the text, and place it in the InspirationalQuote Label.

老实说,到目前为止,我们为这个示例所做的工作就是我最初开始计划时想要涵盖的全部内容。但是,我对随机生成 VisualElements属性有点兴奋,可能使示例变得有点太复杂了。毕竟,我们工程师喜欢过度设计。此示例使用了 Web 请求和 JSON 操作的概念。由于 Web 开发不是本书的重点,因此我不会详细讨论执行这些功能的代码。我希望此示例的重点放在特定于 UI 的代码上,而不是 Web 请求上。我将解释每段代码的作用,但我不一定会逐行解释。

Honestly, what we’ve done up to this point for this example was all I wanted to cover when I initially started planning it. However, I got a bit excited about randomly generating the VisualElements properties and I possibly made the example a bit too complicated. We engineers love to over-engineer, after all. This example uses concepts of web requests and JSON manipulation. Since web development is not a focus of this book, I won’t belabor the code that performs these functions. I want the focus of this example to be on the UI-specific code, not the web requests. I will explain what each section of code does, but I will not necessarily explain it line-by-line.

在开始实际的代码之前,我想简要介绍一下我们将从中获取数据的两个来源。我们将分别使用以下资源来获取猫图片和励志名言:

Before we get to the actual code, I do want to give a brief rundown of the two sources we will be getting our data from. We will use the following resources to get cat pictures and inspirational quotes, respectively:

The Place Kitten 网站允许您只需在 URL 末尾添加尺寸即可获得具有特定尺寸的小猫图片。例如,https ://placekitten.com/300/300显示一张 300 x 300 的小猫图片。当您开发网站时,只需用一些东西填充特定位置,此网站非常适合添加占位符图像。而且,它很可爱!

The Place Kitten website allows you to get a picture of a kitten with a specific dimension by simply adding the dimension to the end of the URL. For example, https://placekitten.com/300/300 displays an image of a kitten that is 300 x 300. This website is great for adding placeholder images when you are developing websites and just need something to fill a specific place. Plus, it’s cute!

Zen Quotes 网站托管了一个 API,允许您获取励志名言。API 是一组允许您与其数据交互的函数。API 具有哪些类型的函数将取决于 API 的用途以及工程师选择创建它的设计范例。但通常,API 将具有获取数据的能力。Zen Quotes 允许您获取励志名言。您可以获取单个名言或一整套名言。访问他们的网站,查看它还有哪些其他选项可用于从其数据库中检索数据。Zen Quotes 以 JSON 格式返回您从其请求的数据。JSON 是一种提供信息的格式标准化。当您获取它时,它将如下所示:

The Zen Quotes website hosts an API that allows you to get inspirational quotes. An API is a collection of functions that let you interact with its data. What types of functions the API has will depend on the use of the API and the design paradigm the engineers chose to create it. But generally, an API will have the ability to GET data. Zen Quotes allows you to GET inspirational quotes. You can GET a single quote or a whole set of them. Visit their site to see what other options it has for retrieving data from its database. Zen Quotes returns the data you request from it in JSON format. JSON is a format standardization that provides information. It will look like the following when you get it:

{“string”:“HelloWorld”,“boolean”:“false”,“number”:“123”,“array”:“[1,2,3]”,“object”:“prop1”:“a”,“prob2”:“b”}}
{"string":"HelloWorld","boolean":false,"number":123,"array":[1,2,3],"object":{"prop1":"a","prob2":"b"}}

但它可以格式化为如下所示:

But it can be formatted to look like the following:

{
  “字符串”:“HelloWorld”,
  “布尔值”:false,
  “数字”:123,
  “大批”: [
    1、
    2、
    3
  ],
  “目的”: {
    “prop1”:“a”,
    “prob2”:“b”
  }
}
{
  "string": "HelloWorld",
  "boolean": false,
  "number": 123,
  "array": [
    1,
    2,
    3
  ],
  "object": {
    "prop1": "a",
    "prob2": "b"
  }
}

我不会详细解释上述代码的含义,但我会指出该示例的重要细节我们会介绍。如果您想了解有关 JSON 格式的更多信息,请参阅以下资源: https: //www.w3schools.com/js/js_json_intro.asp

I won’t get into the specifics of what the preceding code means, but I will point out the important details of the example we cover. If you’d like to learn more about JSON format, see the following resource: https://www.w3schools.com/js/js_json_intro.asp

要完成此示例并让按钮生成猫图片和励志名言,请完成以下步骤:

To finish out this example and have the buttons generate cat pictures and inspirational quotes, complete the following steps:

  1. 在Scripts文件夹中创建一个新的 C# 脚本并将其命名为InspriationalPanel.cs
  2. Create a new C# script in your Scripts folder and call it InspriationalPanel.cs.
  3. 让我们从获取上一节中创建的UIDocument的引用开始。将以下代码添加到您的类中,确保导入UnityEngine.UIElements命名空间:
    私人UIDocument uiDocument;
    无效开始()
    {
        uiDocument = 获取组件<UIDocument>();
    }

    在我们的 Editor 代码示例之后,此代码应该看起来很熟悉;主要区别在于我们使用GetComponent获得了对UIDocument的引用。因此,我们需要将此脚本放在包含我们的 UI Document 组件的同一 GameObject 上。

  4. Let’s start with getting a reference to the UIDocument we created in the previous section. Add the following code to your class, making sure to import the UnityEngine.UIElements namespace:
    private UIDocument uiDocument;
    void Start()
    {
        uiDocument = GetComponent<UIDocument>();
    }

    This code should look familiar after our Editor code example; the main difference is we got the reference to the UIDocument by using GetComponent. So, we’ll need to put this script on the same GameObject that contains our UI Document component.

  5. 继续将此脚本拖到我们的UIDocument GameObject 上。
  6. Go ahead and drag this script onto our UIDocument GameObject.
  7. 现在,使用Start()方法中的以下代码为 UI 文档上的根可视化元素创建一个实例变量
    var root = uiDocument.rootVisualElement;
  8. Now, create an instance variable for the root Visual Element on the UI Documents with the following code in the Start() method:
    var root = uiDocument.rootVisualElement;
  9. 现在让我们连接我们的魅力按钮。添加以下变量声明:
    私人按钮 charmButton;
  10. Now let’s hook up our charm button. Add the following variable declaration:
    private Button charmButton;
  11. 在Start()方法中添加如下代码进行初始化:
    charmButton = root.Q<Button>("CharmButton");
  12. Add the following code to the Start() method to initialize it:
    charmButton = root.Q<Button>("CharmButton");
  13. 我们需要一个在点击charmButton时运行的方法。更新您的代码以显示如下所示。新代码以粗体显示。在我们之前的示例之后,这些代码应该看起来都很熟悉:
    公共类 InspriationalPanel:MonoBehaviour
    {
        私人UIDocument uiDocument;
        私人按钮 charmButton;
        无效开始()
        {
            uiDocument = 获取组件<UIDocument>();
            var root = uiDocument.rootVisualElement;
            charmButton = root.Q<Button>("CharmButton");
            charmButton.clicked += OnCharmClicked;
        }
        私有 void OnCharmClicked()
        {
            // 处理超级按钮点击
        }
        私有无效OnDisable()
        {
            charmButton.clicked-=OnCharmClicked;
        }
    }

    我想添加前面的大块代码,因为它基本上可以作为如何使用UI 工具包编写按钮代码的模板。

  14. We need a method that will run when the charmButton is clicked. Update your code to appear as follows. The new code is bold. This should all look familiar after our previous example:
    public class InspriationalPanel : MonoBehaviour
    {
        private UIDocument uiDocument;
        private Button charmButton;
        void Start()
        {
            uiDocument = GetComponent<UIDocument>();
            var root = uiDocument.rootVisualElement;
            charmButton = root.Q<Button>("CharmButton");
            charmButton.clicked += OnCharmClicked;
        }
        private void OnCharmClicked()
        {
            // Handle charm button click
        }
        private void OnDisable()
        {
            charmButton.clicked -= OnCharmClicked;
        }
    }

    I wanted to add the preceding large block of code because it can essentially act as a template for how you will code buttons with the UI Toolkit.

  15. 现在,我们需要一个对名为CatPic 的可视化元素的引用,以便我们可以更改其背景。将以下代码添加到Start()方法中:
    VisualElement catPic = root.Q<VisualElement>("CatPic");
  16. Now, we need a reference to the Visual Element called CatPic so that we can change its background. Add the following code to the Start() method:
    VisualElement catPic = root.Q<VisualElement>("CatPic");
  17. 你可能注意到我没有将catPic设为类变量,而是将其设为Start()方法中的实例变量。这是因为我们需要引用此代码中的大部分是CatPic的样式,而不是CatPic本身。因此,将以下变量添加到您的类中:
    私人IStyle catPicStyle;
  18. You may notice that I didn’t make catPic a class variable but instead made it an instance variable within the Start() method. This is because the thing we will need to reference the most in this code is the CatPic’s style, not the CatPic itself. So, add the following variable to your class:
    private IStyle catPicStyle;
  19. 现在,将以下内容添加到Start()方法中:
    catPicStyle = catPic.样式;
  20. Now, add the following to the Start() method:
    catPicStyle = catPic.style;
  21. 要获取catPic背景的图像,我们将使用https://placekitten.com/ 。我们将随机选择 150 到 300 之间的宽度和高度,然后从placekitten.com获取适合这些尺寸的图片。为此,我们需要使用协程,因为 Web 请求需要协程。首先创建以下方法:
    IEnumerator 获取猫图片()
    {
        // 在这里实现协程逻辑
    }
  22. To get the image for the catPic background, we’ll use https://placekitten.com/. We’ll randomly pick a width and height between 150 and 300, then get a picture from placekitten.com that fits those dimensions. To do so, we will need to use a coroutine since web requests require coroutines. Start by creating the following method:
    IEnumerator GetCatPic()
    {
        // Implement coroutine logic here
    }

笔记

Note

在您输入 Web 请求之前,此方法将在您的 IDE 中显示错误

This method is going to show an error in your IDE until you put in the web request.

确保将以下命名空间添加到您的代码中,以便它能够识别IEnumerator是什么:

Make sure to add the following namespace to your code so it recognizes what an IEnumerator is:

使用System.Collections;
using System.Collections;
  1. 现在,让我们随机生成我们将请求的图像的宽度和高度。将以下内容添加到 GetCatPic ()协程中以获取随机数,然后更改catPic的尺寸以匹配它:
    int 随机宽度 = 随机范围(150, 300);
    int 随机高度 = 随机范围(150, 300);
    catPicStyle.宽度 = 随机宽度;
    catPicStyle.高度 = 随机高度;
  2. Now, let’s randomly generate the width and height of the image we will request. Add the following to your GetCatPic() coroutine to get random numbers and then change the catPic’s dimensions to match it:
    int randomWidth = Random.Range(150, 300);
    int randomHeight = Random.Range(150, 300);
    catPicStyle.width = randomWidth;
    catPicStyle.height = randomHeight;
  3. 请记住,placekitten 图像的 URL 格式为https://placekitten.com/300/300。因此,我们需要创建一个字符串,用randomWidthrandomHeight替换两个300值。将以下代码行添加到您的GetCatPic()协程中:
    字符串 uri = “https://placekitten.com/” + randomWidth + “/” + randomHeight;
  4. Remember, the format of the URL for a placekitten image is https://placekitten.com/300/300. So, we need to create a string that replaces the two 300 values with randomWidth and randomHeight. Add the following line of code to your GetCatPic() coroutine:
    string uri = "https://placekitten.com/" + randomWidth + "/" + randomHeight;
  5. 现在,让我们发送一个网络请求到uri。将以下代码添加到GetCatPic()协程中:
    UnityWebRequest 请求 = UnityWebRequestTexture.GetTexture(uri);
    产生返回请求.SendWebRequest();
    如果(请求st.result != UnityWebRequest.Result.Success){
         调试.日志(请求.错误);
    } 别的 {
         // 使用返回的数据进行处理
    }

    上述代码尝试从网站获取纹理。如果请求失败, if会打印错误。确保导入以下命名空间,以便您的脚本了解UnityWebRequest是什么:

    使用 UnityEngine.Networking;
  6. Now, let’s send a web request to the uri. Add the following code to your GetCatPic() coroutine:
    UnityWebRequest request = UnityWebRequestTexture.GetTexture(uri);
    yield return request.SendWebRequest();
    if (request.result != UnityWebRequest.Result.Success){
         Debug.Log(request.error);
    } else {
         // Do stuff here with returned data
    }

    The preceding code tries to get a texture from a website. The if prints an error if the request fails. Make sure to import the following namespace so your script understands what a UnityWebRequest is:

    using UnityEngine.Networking;
  7. 现在,让我们用以下代码替换//Do stuff here with returned data部分
    Texture2D myTexture = ((DownloadHandlerTexture)request.downloadHandler).texture;
    Debug.Log("已获取纹理");
    catPicStyle.backgroundImage = 新的StyleBackground(myTexture);

    此代码将获取 Web 请求返回的图片并将其存储为 Texture2D 。然后将catPicStyle的背景图像设置为该Texture2D图像。

  8. Now, let’s replace the // Do stuff here with returned data part with the following code:
    Texture2D myTexture = ((DownloadHandlerTexture)request.downloadHandler).texture;
    Debug.Log("Texture Acquired");
    catPicStyle.backgroundImage = new StyleBackground(myTexture);

    This code will take the picture returned by the web request and store it as a Texture2D. It then sets the background image of catPicStyle to that Texture2D image.

  9. 现在,我们需要按钮来实际调用GetCatPic()协程。更新OnCharmClicked()方法以调用GetCatPic()协程:
    私有 void OnCharmClicked()
    {
        启动协同程序(GetCatPic());
    }

    如果你玩游戏,你现在可以点击“Charm Me”按钮来获取随机猫图片。

  10. Now, we need to have our button to actually call the GetCatPic() coroutine. Update the OnCharmClicked() method to call the GetCatPic() coroutine:
    private void OnCharmClicked()
    {
        StartCoroutine(GetCatPic());
    }

    If you play the game, you can now click the Charm Me button to get random cat pictures.

图 18.73:我们的 UI 中随机出现猫的图片

图 18.73:我们的 UI 中随机出现猫的图片

Figure 18.73: Random cat pictures appearing in our UI

  1. 如果再次单击该按钮,您可能会注意到图像有点奇怪。由于GetCatPic()方法包含 Web 请求,因此需要花一点时间来获取图像,然后将其转换为纹理,因此在加载新图像之前,当前位于其位置的任何图像都会调整大小,变得扭曲。

    为了避免这种情况发生,我们在加载新图像时删除原始图像。将以下代码添加为GetCatPic()协程的第一行:

    catPicStyle.backgroundImage = null;
  2. You may notice a little funkiness with the image if you click the button a second time. Because the GetCatPic() method contains a web request, it takes a moment to fetch the image and then turn it into a texture, so whatever image is currently in its place will resize, becoming distorted, before the new image loads in.

    Instead of letting this happen, let’s remove the original image while the new image loads in. Add the following code as the first line of your GetCatPic() coroutine:

    catPicStyle.backgroundImage = null;
  3. 现在,让我们设置“激励我”按钮的功能。将以下变量声明添加到您的类中:
    私人按钮激发按钮;
    自有品牌 inspirationalQuote;
  4. Now, let’s set up the functionality of the Inspire Me button. Add the following variable declarations to your class:
    private Button inspireButton;
    private Label inspirationalQuote;
  5. 添加以下内容当点击inspireButton 时将调用的方法:
    私有 void OnInspireClicked()
    {
        // 实现单击 InspireButton 时的功能
    }
  6. Add the following method that will be called when the inspireButton is clicked:
    private void OnInspireClicked()
    {
        // Implement functionality for when the inspireButton is clicked
    }
  7. 将以下代码添加到Start()方法中。现在你应该很熟悉这些代码了:
    inspireButton = root.Q<Button>("InspireButton");
    inspireButton.clicked += OnInspireClicked;
    inspirationalQuote = root.Q<Label>("InspirationalQuote");
  8. Add the following code to the Start() method. This should all look familiar to you by now:
    inspireButton = root.Q<Button>("InspireButton");
    inspireButton.clicked += OnInspireClicked;
    inspirationalQuote = root.Q<Label>("InspirationalQuote");
  9. 现在,让我们继续填写励志名言的数据。我们将使用https://zenquotes.io/api/random请求来检索随机名言。如果您在 Web 浏览器中导航到该 URL,则可以看到它返回的数据的格式。将以下代码添加到您的InspriationalPanel.cs脚本中:
    IEnumerator GetInspiringQuote() {
         UnityWebRequest 请求 = UnityWebRequest.Get("https://zenquotes.io/api/random");
         产生返回请求.SendWebRequest();
         如果(请求.结果!= UnityWebRequest.结果.成功){
              调试.日志(请求.错误);
         } 别的 {
              Debug.Log("已获取报价");
              字符串响应=请求.downloadHandler.文本;
              调试.日志(响应.ToString());
              // 更多代码将放在这里
         }
    }

    你会注意到,这与我们用来获取猫图像的 Web 请求的结构类似。在我们继续,我们来探索一下数据是如何返回的。

  10. Now, let’s move on to filling in the data for the inspirational quote. We will use the https://zenquotes.io/api/random request to retrieve a random quote. If you navigate to that URL in your web browser, you can see how the data it returns is formatted. Add the following code to your InspriationalPanel.cs script:
    IEnumerator GetInspiringQuote() {
         UnityWebRequest request = UnityWebRequest.Get("https://zenquotes.io/api/random");
         yield return request.SendWebRequest();
         if (request.result != UnityWebRequest.Result.Success) {
              Debug.Log(request.error);
         } else {
              Debug.Log("Quote Acquired");
              string response = request.downloadHandler.text;
              Debug.Log(response.ToString());
              // more code will go here
         }
    }

    You’ll notice this is structured similarly to the web request we used to get our cat image. Before we proceed, let’s explore how the data is returned.

  11. 使用以下代码更新您的OnInspireClicked()方法
    私有 void OnInspireClicked()
    {
        启动协同程序(GetInspiringQuote());
    }
  12. Update your OnInspireClicked() method with the following code:
    private void OnInspireClicked()
    {
        StartCoroutine(GetInspiringQuote());
    }
  13. 玩游戏并点击“激励我”按钮。您将在控制台中看到类似以下内容:
    图 18.74: API 请求响应

    图 18.74: API 请求响应

    如您所见,我们无法将完整结果放入标签的文本字段中。我们需要将其格式化为可用的格式,并仅获取我们想要的信息。为此,大多数简单来说,我们需要另一个包。

    使用窗口|包管理器打开包管理器

  14. Play the game and click the Inspire Me button. You will see something like the following in the console:

    Figure 18.74: The API request response

    As you can see, we can’t exactly put the full result into our Label’s text field. We need to format this into something usable and get only the information we want. To do this most simply, we’ll need another package.

    Open the Package Manager with Window | Package Manager.

  1. 选择加号并选择从git URL添加包...。
  2. Select the plus sign and select Add package from git URL….
  3. 在文本框中输入com.unity.nuget.newtonsoft-json并选择Add。加载后,您应该会在包列表中看到Newtonsoft Json。这将允许您轻松操作JSON 数据。
  4. Type com.unity.nuget.newtonsoft-json into the textbox and select Add. After some loading, you should see Newtonsoft Json in your packages list. This will allow you to easily manipulate JSON data.
  5. 返回到您的脚本并添加以下命名空间:
    使用 Newtonsoft.Json.Linq;

    如果您的 IDE 无法识别Newtonsoft ,请关闭 IDE 和 Unity,然后重新打开。

  6. Return to your script and add the following namespace:
    using Newtonsoft.Json.Linq;

    If Newtonsoft is not recognized by your IDE, close the IDE and Unity and reopen it.

  7. 现在,我们可以解析从 API 调用收到的数据并获取我们想要的信息。API 调用返回的数据以[开头。这意味着我们收到的数据以数组格式发送给我们。因此,我们需要将数据转换为数组。将//more code will go here替换为以下内容:
    JArray jArray = JArray.Parse(响应);
  8. Now, we can parse the data we receive from our API call and get just the information we want. The data that is returned by the API call starts with [. This means that the data we are receiving is coming to us in an array format. So, we need to convert the data to an array. Replace // more code will go here with the following:
    JArray jArray = JArray.Parse(response);
  9. 我们只想要分配给“q”属性和“a”属性的字符串,因为它们分别代表引文和作者。要获取这些数据,我们需要将数据设为对象格式。返回给我们的数组只有一个数组项,因此请使用以下代码将数组项转换为对象:
    JObject jObject = JObject.Parse(jArray[0].ToString());
  10. We only want the strings assigned to the "q" property and the "a" property, as these represent the quote and the author respectively. To get this data, we need our data to be in an object format. The array that is returned to us only has a single array item, so use the following code to convert the array item to an object:
    JObject jObject = JObject.Parse(jArray[0].ToString());
  11. 现在,我们可以获取所需的字符串。添加以下几行以获取引文和作者:
    字符串引用 = (字符串)jObject["q"];
    字符串作者 = (字符串)jObject["a"];
  12. Now, we can get the strings we want. Add the following lines to get the quote and the author:
    string quote = (string)jObject["q"];
    string author = (string)jObject["a"];
  13. 最后要做的是将标签的文本更改为我们想要的信息,并确保其格式正确正确。我希望引文出现在引号中。然后,在下一行,我希望看到类似~ Author's Name 的内容。以下代码行将实现此目的:
    inspirationalQuote.text = "\"" + quote + "\" \n~" + author;

    玩游戏,您现在应该能够获得小猫的图片和励志名言!

  14. The last thing to do is change the label’s text to the information we want and make sure it’s formatted correctly. I want the quote to appear in quotes. Then, on the next line, I want to see something like ~ Author's Name. The following line of code will do that:
    inspirationalQuote.text = "\"" + quote + "\" \n~" + author;

    Play the game and you should now be able to get pictures of kittens and inspirational quotes!

图 18.75:我们示例的最终版本

图 18.75:我们示例的最终版本

Figure 18.75: The final version of our example

由于 zenquotes 是一个 API,开发人员将调用次数限制为每 30 秒 5 次。这意味着如果您在 30 秒内尝试点击按钮超过 5 次,您将收到错误消息。

Because zenquotes is an API, the developers have put a limit of 5 calls per 30 seconds. So, that means if you try clicking the button more than 5 times in 30 seconds, you will get an error message.

你可以扩展这一点例如,在开始时获取一组数据并将其存储在本地。这可以减少一些图像的加载时间并减少所需的 API 调用次数。

You could expand on this example by getting a set of the data at the start and storing it locally. This could reduce some of the load times of the images and reduce the number of API calls needed.

这就是我将介绍的 UI Toolkit 的所有示例。正如我之前所说,这是一个大话题,也是开发 UI 的全新思路。我甚至无法介绍我想要介绍的内容的一半。如果您喜欢使用 UI Toolkit,我建议您查看资源部分以获取建议的进一步阅读和教程。

That’s all for the examples that I will cover for the UI Toolkit. As I said previously, this is a big topic and a whole new way of thinking about developing UI. I wasn’t able to cover even half of what I would have liked to. If you enjoy working with the UI Toolkit, I recommend viewing the resources section for suggested further reading and tutorials.

资源

Resources

如果您正在寻找更多文档,我推荐Unity 提供的以下资源:

If you’re looking for more documentation, I recommend the following resources provided by Unity:

如果您对教程感兴趣,这里有一些很棒的资源。需要注意的是,目前大多数教程都是关于使用 UI Toolkit for Editor UI 的,因为运行时支持仍然很新:

If you’re interested in tutorials, here are some great resources. It’s important to note that most of the tutorials at this point are for using the UI Toolkit for Editor UI since the runtime support is still new:

最后,Unity 提供了一些使用 UI Toolkit 的优秀预制项目。您可以在资产商店中找到这些项目:

Lastly, Unity has provided some excellent pre-made projects that use the UI Toolkit. You can find these projects on the asset store here:

概括

Summary

UI Toolkit 仍在开发中,但 Unity 正在积极将其开发为一个系统,以取代当前的 uGUI 系统(本书的其余部分到目前为止一直关注该系统)。它使用 Web 开发的概念来开发 UI,并且可以提供比 uGUI 更简洁、性能更高的 UI。但是,由于它仍处于开发阶段,因此它还不能完成 uGUI 所做的所有事情……目前还不能。虽然您可能无法完全过渡到 UI Toolkit 系统来满足您的 UI 需求,但了解它的工作原理会很有帮助,因为有一天,它将是您唯一的选择。

The UI Toolkit is still in development, but Unity is actively working on it as a system to replace the current uGUI system (that the rest of this book has focused on up to this point). It uses the concepts of web development to develop UI and can provide a cleaner, more performant UI than uGUI. However, since it is still in development, it doesn’t do everything uGUI does … yet. While you might not be able to fully transition over to the UI Toolkit system for your UI needs, it is helpful to have an idea of how it works, since one day, it will be your only option.

为了帮助您介绍 UI 工具包的概念,我们讨论了系统的一般概念以及如何使用它在编辑器和运行时 UI 中制作 UI。

To help introduce the concepts of the UI Toolkit to you, we discussed the general concepts of the system as well as how to use it to make UI in the Editor and Runtime UI.

在下一章中,我们将讨论Unity 中使用的另一个 UI 系统:IMGUI。

In the next chapter, we will discuss yet another UI system used within Unity: IMGUI.

19

19

使用 IMGUI

Working with IMGUI

我将介绍的最后一个 UI 系统是IMGUI,即即时模式图形用户界面。IMGUI 的主要用途是创建在开发和调试过程中协助开发人员的工具。虽然 IMGUI 在技术上可以制作运行时 UI,但 Unity 强烈反对这样做。因此,例如,您可以使用它来制作编辑器扩展或调试菜单,这些菜单将在您的游戏视图中运行,并且可以在编辑器外玩游戏时访问。但是,请记住,这些调试游戏内菜单是面向开发人员的,而不是面向玩家的。IMGUI 最常用于开发编辑器UI 扩展。

The last UI system I will cover is IMGUI or Immediate Mode Graphical User Interface. The primary usage for IMGUI is to create tools that assist developers during development and debugging. While IMGUI can technically make runtime UI, it is strongly discouraged by Unity. So, for example, you can use it to make Editor extensions or debug menus that will run in your game’s view, and can be accessed when you play your game outside of the Editor. However, keep in mind these debug in-game menus are meant to be developer-facing, not player-facing. IMGUI is most commonly used for developing Editor UI extensions.

由于本书主要关注运行时、面向玩家的 UI,因此我不会深入探讨这个系统;但是,我将介绍使用 IMGUI 制作面向开发人员的基础知识。

Since this book’s primary focus is on runtime, player-facing UI, I won’t delve too deep into this system; however, I’ll cover the very basics of making developer-facing with IMGUI.

在本章中,我将讨论以下内容:

In this chapter, I will discuss the following:

  • IMGUI 使用方法概述
  • A general overview of how to use IMGUI
  • 最常用的IMGUI 控件
  • The most commonly used IMGUI Controls
  • 如何使用 IMGUI 进行Inspector UI
  • How to use IMGUI for Inspector UI
  • 如何在游戏中显示调试帧速率文本 UI
  • How to show debug frame rate text UI in your game
  • 如何在 Inspector 组件上放置按钮并将数据导入ScriptableObject
  • How to put a button on an Inspector component and import data to a ScriptableObject

值得注意的是,IMGUI 不是 Unity 推荐的系统。当谈到运行时 UI 时,他们推荐 uGUI(本书的大部分内容都是关于这个的),而当谈到编辑器 UI 时,他们推荐 UI Toolkit(第 18 章中介绍过)。因此,学习 IMGUI 并不是 Unity 开发人员的必备技能。对于非程序员来说,它尤其不是一项必备技能。然而,它确实为程序员提供了一种非常快速的方法来构建 UI 以协助他们进行开发,因此这些技能并非毫无用处。

It is important to note that IMGUI is not a recommended system by Unity. When it comes to runtime UI, they recommend uGUI (which is what the majority of this book is about), and when it comes to Editor UI, they recommend UI Toolkit (which was covered in Chapter 18). So, learning IMGUI is not necessarily a required skill for anyone developing in Unity. It is especially not a required skill for non-programmers. However, it does give programmers a very quick way to build out UI to assist them in development, so the skills are not useless.

让我们回顾一下有关IMGUI的一些基本信息。

Let’s review some basic information about IMGUI.

技术要求

Technical requirements

您可以在此处找到本章的相关代码和资产文件https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2019

You can find the relevant codes and asset files of this chapter here: https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2019

IMGUI 概述

IMGUI overview

正如我之前所说,IMGUI 提供程序员可以快速构建 UI 来协助他们进行开发。这是因为 IMGUI 完全通过代码构建。它不连接到 GameObjects,所有对象都通过调用OnGUI()OnInspectorGUI()方法进行渲染。OnGUI ()方法每帧都会被调用,类似于Update()方法。

As I stated earlier, IMGUI gives programmers a quick way to build out UI that can assist them in their development. This is because IMGUI is built exclusively via code. It is not connected to GameObjects, and all objects are rendered via calls to an OnGUI() or OnInspectorGUI() method. The OnGUI() method is called every frame, similar to the Update() method.

如果您希望 IMGUI 出现在场景中,请在MonoBehaviour继承脚本中的OnGUI()方法中编写所有 UI 构建代码。由于 IMGUI 项目是通过代码创建的,因此您在MonoBehaviour脚本上使用它创建的任何 UI 都将在游戏运行之前呈现。

If you want your IMGUI to appear within your scene, you write all your UI building code in an OnGUI() method within a MonoBehaviour inheriting script. Because IMGUI items are created via code, any UI you create with it on a MonoBehaviour script will not render until the game is run.

如果您希望 UI 显示在编辑器窗口中,则应将所有 UI 构建代码写入EditorWindow继承脚本中的OnGUI()方法中。如果您希望 UI 显示在检查器中,则应将所有 UI 构建代码写入Editor继承脚本中的OnInspectorGUI()方法中

If you want your UI to appear in an Editor window, you will write all your UI building code in an OnGUI() method within an EditorWindow inheriting script. If you want your UI to appear in the Inspector, you write all your UI building code in an OnInspectorGUI() method within an Editor inheriting script.

所有 IMGUI 项目都是通过在OnGUI()OnInspectorGUI()方法中调用其独特方法创建的。每个独特方法都可以使用矩形位置进行定位和调整大小。使用矩形位置定位对象时,首先使用Rect()方法创建一个新的矩形。Rect ()方法采用四个参数:x位置、y位置、宽度高度。因此,例如,您可以使用以下代码行创建一个新的矩形:

All IMGUI items are created by calling their unique method within an OnGUI() or OnInspectorGUI() method. Each of their unique methods can be positioned and sized using rectangular position. When positioning an object with rectangular position, you first create a new Rectangle using the Rect() method. The Rect() method takes four parameters: x position, y position, width, and height. So, for example, you can create a new rectangle with the following line of code:

矩形 rect = 新矩形 (10, 10, 50, 50);
Rect rect = new Rect(10, 10, 50, 50);

这将在屏幕坐标(10, 10)处创建一个宽度和高度为50矩形

This would create a rectangle at screen coordinate (10, 10) with a width and height of 50.

请记住,屏幕坐标将位置(0,0)置于屏幕的左上角。

Remember, screen coordinates put position (0,0) in the top-left corner of the screen.

您还可以使用Vector2来指定参数。例如,您可以执行如下操作来实现相同的结果:

You can also use Vector2s to specify the parameters. For example, you could do something like this to achieve the same results:

Vector2 位置 = 新 Vector2(10, 10);
Vector2 维度 = new Vector2(50, 50);
Rect rect = new Rect(位置,尺寸);
Vector2 position = new Vector2(10, 10);
Vector2 dimensions = new Vector2(50, 50);
Rect rect = new Rect(position, dimensions);

现在我们知道如何创建 IMGUI 项目,让我们回顾一下可以使用OnGUI()绘制的项目类型

Now that we know how to create IMGUI items, let’s review the types of items that you can draw using OnGUI().

IMGUI 控件

IMGUI Controls

绘制的项目使用 IMGUI 的控件称为控件。虽然“控件”一词可能暗示该项目是可交互的,但并非所有控件是可交互的。IMGUI 系统提供多种控件,但您可以使用以下控件实现大多数 IMGUI 目标:

The items drawn using IMGUI are called Controls. While the term “Controls” may imply the item is interactable, not all Controls are interactable. There are multiple Controls available with the IMGUI system, but you can accomplish most of your IMGUI goals with the following:

  • 标签
  • Label
  • 按钮
  • Button
  • 重复按钮
  • RepeatButton
  • 文本框
  • TextField
  • 文本区域
  • TextArea
  • 切换
  • Toggle

笔记

Note

您可以在此处找到所有 IMGUI 控件的完整列表https ://docs.unity3d.com/2023.3/Documentation/Manual/gui-Controls.xhtml 。

You can find a comprehensive list of all IMGUI controls here: https://docs.unity3d.com/2023.3/Documentation/Manual/gui-Controls.xhtml.

Label是非交互式项目。它可以是文本或图像。要创建Label,请调用GUI.Label()方法。GUI.Label ()方法多个重载,但主要重载是您将使用以下工具:

A Label is a non-interactive item. It can be either text or an image. To create a Label, you call the GUI.Label()method. The GUI.Label() method has multiple overloads, but the primary ones you will use are the following:

公共静态void标签(矩形位置,字符串文本);
公共静态无效标签(矩形位置,纹理图像);
public static void Label(Rect position, string text);
public static void Label(Rect position, Texture image);

其中第一个用于定义文本标签,第二个用于定义图像标签。

Where the first is used to define a text label, and the second is used to define an image label.

例如,以下代码当它附加到 GameObject 并且在Inspector中分​​配labelTexture变量时,将在场景中显示文本标签和图像标签:

For example, the following code will display a text label and an image label in a scene, when it is attached to a GameObject and the labelTexture variable is assigned in the Inspector:

公共类第 19 章标签:MonoBehaviour {
    公共Texture2D标签纹理;
    私有无效OnGUI(){
        GUI.Label(new Rect(10, 10, 100, 50), "文本标签");
        GUI.Label(new Rect(10, 80, 50, 50), labelTexture);
    }
}
public class Chapter19Labels : MonoBehaviour {
    public Texture2D labelTexture;
    private void OnGUI() {
        GUI.Label(new Rect(10, 10, 100, 50), "Text Label");
        GUI.Label(new Rect(10, 80, 50, 50), labelTexture);
    }
}

标签看起来如下:

The labels will look as follows:

图 19.1:使用 IMGUI 标签

图 19.1:使用 IMGUI 标签

Figure 19.1: Using IMGUI Labels

按钮控件只需单击即可执行一项功能。单击并按住按钮时,它只会触发一次。它可以可以是文本按钮或图像按钮。要创建按钮,请调用GUI.Button()方法。GUI.Button ()方法有多个重载,但您将使用的主要重载如下

A Button Control performs a function with a single click. It only triggers once if the button is clicked and held. It can be a text button or an image button. To create a Button, you call the GUI.Button() method. The GUI.Button() method has multiple overloads, but the primary ones you will use are the following:

公共静态 bool 按钮(矩形位置,字符串文本);
公共静态bool按钮(矩形位置,纹理图像);
public static bool Button(Rect position, string text);
public static bool Button(Rect position, Texture image);

第一个将用于定义一个文本按钮,第二个将用于定义一个图像按钮。

Where the first will be used to define a text button, and the second will be used to define an image button.

要在单击按钮时执行代码,请在OnGUI()方法内的if语句中创建按钮

To execute code when a button is clicked, you create the button within an if statement within the OnGUI() method.

例如,以下代码将在场景中显示一个文本按钮和一个图像按钮:

For example, the following code will display a text button and an image button in a scene:

公共类第 19 章按钮:MonoBehaviour {
    公共 Texture2D 按钮纹理;
    私有无效OnGUI(){
        如果(GUI.Button(new Rect(10, 10, 100, 50),“文本按钮”)){
            Debug.Log("文本按钮被点击");
        }
        如果(GUI.Button(新Rect(10,80,50,50),buttonTexture)){
            Debug.Log("图像按钮被点击");
        }
    }
}
public class Chapter19Buttons : MonoBehaviour {
    public Texture2D buttonTexture;
    private void OnGUI() {
        if (GUI.Button(new Rect(10, 10, 100, 50), "Text Button")) {
            Debug.Log("Text Button Clicked");
        }
        if (GUI.Button(new Rect(10, 80, 50, 50), buttonTexture)) {
            Debug.Log("Image Button Clicked");
        }
    }
}

两个按钮都会写入每次单击时,控制台中都会显示一条消息。代码必须附加到 GameObject,并且必须在Inspector中分​​配buttonTexture变量。

Both buttons will write a message in the console each time they are clicked. The code must be attached to a GameObject and the buttonTexture variable must be assigned in the Inspector.

上述代码创建的按钮如下所示:

The buttons created by the preceding code will look as follows:

图 19.2:使用 IMGUI 按钮

图 19.2:使用 IMGUI 按钮

Figure 19.2: Using IMGUI Buttons

如果你想创建一个按钮,在点击并按住时调用一个方法,你可以使用RepeatButton。它的创建代码与创建Button几乎相同。你可以将上述代码中的所有GUI.Button实例替换为GUI.RepeatButton,并实现结果类似,不同之处在于只要按住按钮,按钮就会执行该方法。

If you want to create a button that calls a method as it is clicked and held, you can use a RepeatButton. Its creation code is nearly identical to the creation of a Button. You can replace all instances of GUI.Button in the preceding code with GUI.RepeatButton and achieve a similar result, except the button will execute the method as long as the button is held.

TextField和TextArea控件允许您创建一个可编辑的交互式框文本。当您只需要一行可编辑文本时,可以使用TextField ;而当您需要多行文本时,可以使用TextArea 。要创建TextFieldTextArea,请分别调用GUI.TextField()GUI.TextArea()方法。GUI.TextField ()方法有多个重载,但您将使用的主要方法如下:

The TextField and TextArea Controls allow you to create an interactive box with editable text. TextField is used when you want only a single line of editable text, whereas TextArea is used when you want multiple lines. To create a TextField or TextArea, you call the GUI.TextField() and GUI.TextArea() methods, respectively. The GUI.TextField() method has multiple overloads, but the primary ones you will use are the following:

公共静态字符串 TextField (矩形位置,字符串文本);
公共静态字符串 TextArea(矩形位置,字符串文本);
public static string TextField(Rect position, string text);
public static string TextArea(Rect position, string text);

第一个方法创建一个TextField,第二个方法创建一个TextArea。在这两种情况下,字符串参数都是在用户开始编辑文本之前显示的文本。请注意,该方法返回的是字符串类型。您可以通过将构造的对象分配给字符串变量来获取用户输入的值

The first method creates a TextField, and the second creates a TextArea. In both cases, the string parameter is the text that will be displayed before the user begins editing the text. Note that the method returns a string type. You can get the value of what is entered by the user by assigning the constructed object to a string variable.

例如,以下代码将显示一个TextField一个 TextArea,当它附加到GameObject 时,您可以在场景中与之交互:

For example, the following code will display a TextField and TextArea you can interact with in your scene when it is attached to a GameObject:

公共类第 10 章 TextFieldAndArea : MonoBehaviour {
    private string textFieldText = "输入文本";
    private string textAreaText = "输入文本";
    私有无效OnGUI(){
        文本字段文本 = GUI.文本字段(新Rect(10, 10, 100, 50), 文本字段文本);
        文本区域文本 = GUI.文本区域(新 Rect(10, 80, 100, 100), 文本区域文本);
    }
}
public class Chapter10TextFieldAndArea : MonoBehaviour {
    private string textFieldText = "Enter text";
    private string textAreaText = "Enter text";
    private void OnGUI() {
        textFieldText = GUI.TextField(new Rect(10, 10, 100, 50), textFieldText);
        textAreaText = GUI.TextArea(new Rect(10, 80, 100, 100), textAreaText);
    }
}

在用户开始编辑文本之前出现如下场景:

The items rendered in the scene appear as follows before the user starts editing the text:

图 19.3:使用 IMGUI TextField 和 TextArea 控件

图 19.3:使用 IMGUI TextField 和 TextArea 控件

Figure 19.3: Using the IMGUI TextField and TextArea Controls

Toggle控件是一个复选框,单击即可改变其状态。要创建Toggle,请调用GUI.Toggle()方法。GUI.Toggle ()方法有多个重载,但您主要需要的是将使用以下内容:

A Toggle Control is a checkbox that changes its state with a click. To create a Toggle, you call the GUI.Toggle() method. The GUI.Toggle() method has multiple overloads, but the primary ones you will use are the following:

公共静态 bool Toggle(Rect 位置,bool 值,字符串文本);
public static bool Toggle(Rect position, bool value, string text);

注意该方法返回的是bool类型,你可以通过将创建的对象赋值给bool变量来获取切换按钮的值

Note that the method returns a bool type. You can get the value of the toggle by assigning the created object to a bool variable.

例如,以下代码将创建一个切换按钮,每当单击该切换按钮时,其值将分配给布尔变量。与其他示例一样,此示例需要附加到场景中的 GameObject才能渲染:

For example, the following code will create a toggle whose value is assigned to a Boolean variable whenever the toggle is clicked. This example, like the other examples, would need to be attached to a GameObject in your scene to render:

公共类第 19 章切换:MonoBehaviour {
    私有 bool toggleBool = true;
    私有无效OnGUI(){
        toggleBool = GUI.Toggle(new Rect(10, 10, 100, 50), toggleBool,"切换我");
    }
}
public class Chapter19Toggle : MonoBehaviour {
    private bool toggleBool = true;
    private void OnGUI() {
        toggleBool = GUI.Toggle(new Rect(10, 10, 100, 50), toggleBool,"Toggle Me");
    }
}

切换按钮最初将在场景中按如下方式呈现,直到用户与其交互,在这种情况下,切换按钮将在每次点击时打开和关闭。

The toggle will initially render in the scene as follows until the user interacts with it, in which case the toggle will turn on and off with each click.

图 19.4:使用 IMGUI 切换控件

图 19.4:使用 IMGUI 切换控件

Figure 19.4: Using the IMGUI Toggle Control

我展示的所有示例都在场景中。但是,如果您想在编辑器窗口中显示 UI,您的代码将以类似的方式工作。您只需将代码放在EditorWindow继承脚本中即可。(请参阅第 18 章中介绍的示例。)但是,如果您想在 Inspector 中创建 IMGUI,情况会略有不同。现在让我们看看。

All the examples I have shown were in the scene. However, if you want to display your UI in an Editor window, your code will work similarly. You simply put your code in an EditorWindow inheriting script. (See the example covered in Chapter 18.) However, it is slightly different if you want to create IMGUI in your Inspector. Let’s look at that now.

检查器中的 IMGUI

IMGUI in the Inspector

使用 IMGUI 增强组件的方式与使用游戏内 IMGUI 和EditorWindow IMGUI的方式非常相似。但是,有一些小的变化。首先,您要编写从Editor继承的脚本。其次,您使用OnInspectorGUI()方法,而不是OnGUI()方法。第三,如果您希望 Inspector 还包含其所有常用数据,则需要在OnInspectorGUI()方法中调用DrawDefaultInspector()方法。最后,如果您希望 IMGUI 按钮与各种默认 Inspector 元素一致,您使用GUILayout基类,而不是GUI基类。因此,例如,您不会使用以下代码创建按钮:

Using IMGUI to enhance your components works very similarly to the way it does with in-game IMGUI and EditorWindow IMGUI. However, there are some small changes. First, you write scripts that inherit from Editor. Second, you use the OnInspectorGUI() method, not the OnGUI() method. Third, if you want the Inspector to also contain all its usual data, you need to call the DrawDefaultInspector() method within your OnInspectorGUI() method. Lastly, if you want the IMGUI button to appear in line with the various default Inspector elements, you use the GUILayout base class, rather than the GUI base class. So, for example, you wouldn’t create a button with the following code:

GUI.Button(new Rect(10, 10, 100, 50), "文本按钮");
GUI.Button(new Rect(10, 10, 100, 50), "Text Button");

相反,你可以用这个来创建它:

Instead, you’d create it with this:

GUILayout.Button("文本按钮");
GUILayout.Button("Text Button");

使用GUILayout创建的按钮不需要矩形位置,并且会自动定位在UI 内。

Buttons created with GUILayout do not require a rectangular position and will automatically be positioned within the UI.

我将在示例部分介绍一个这样的例子

I will cover an example of this in the Examples section.

现在,您已经掌握了足够的基本背景信息,可以开始使用 IMGUI 开发面向开发人员的 UI。让我们看一些基本示例,帮助您开始使用该系统。

Now, you have enough basic background information to begin developing developer-facing UI with IMGUI. Let’s look at some basic examples to get you started on using the system.

示例

Examples

对于本章中的示例,我将介绍两种类型的 IMGUI 用法:一种用于游戏内调试菜单,另一种用于检查器 UI。到目前为止介绍的所有示例都是游戏内调试菜单 UI。

For the examples in this chapter, I will cover two types of IMGUI usages: one for an in-game debug menu and another for an Inspector UI. All the examples covered up to this point have all been in-game debug menu UI.

使用 IMGUI 显示游戏中的帧速率

Using IMGUI to show framerate in-game

让我们创建一个非常简单的脚本,它将在场景中显示游戏的帧速率。如果帧速率低于某个值,它将改变颜色。

Let’s create a very simple script that will show the frame rate of our game in the scene. It will change color if the framerate drops below a certain value.

要显示帧速率游戏,完成以下步骤:

To display the framerate in your game, complete the following steps:

  1. 创建一个名为Chapter19-Examples.unity的新场景。
  2. Create a new scene called Chapter19-Examples.unity.
  3. 在新场景中,创建一个名为DebugMenu的新 GameObject 。
  4. Within the new scene, create a new GameObject called DebugMenu.
  5. 创建一个名为DebugFrameRate.cs的新脚本。
  6. Create a new script called DebugFrameRate.cs.
  7. DebugFrameRate.cs脚本作为组件附加到你的DebugMenu GameObject。
  8. Attach the DebugFrameRate.cs script to your DebugMenu GameObject as a component.
  9. 打开新脚本并添加以下变量声明:
    int fps;
    [序列化字段] int fpsThreshold;

    fps变量用于估计游戏的帧速率,而fpsThreshold变量在 Inspector 中分​​配并用于确定 fps 在场景中显示为红色的阈值。

  10. Open the new script and add the following variable declarations:
    int fps;
    [SerializeField] int fpsThreshold;

    The fps variable is used to get an estimate of our game’s frame rate, while the fpsThreshold variable is assigned in the Inspector and used to determine the threshold for when our fps will display as red in the scene.

  11. 使用以下代码创建一个显示 fps 的IMGUI标签
    私有无效OnGUI(){
            GUI.Label(新Rect(10,10,50,50),“fps =”+fps.ToString());
    }
  12. Create an IMGUI Label that will display the fps with the following code:
    private void OnGUI() {
            GUI.Label(new Rect(10, 10, 50, 50), "fps = " + fps.ToString());
    }
  13. 现在,让我们计算一下 fps。我们可以使用以下方法估算 fps :
    私有无效更新(){
        fps = (int)(1f / Time.unscaledDeltaTime);
    }
  14. Now, let’s calculate the fps. We can get an estimate of the fps with the following:
    private void Update() {
        fps = (int)(1f / Time.unscaledDeltaTime);
    }
  15. 玩游戏,你应该会看到场景中显示的 fps。如果你打开“统计”窗口,你应该看到这些值比较接近。
    图 19.5:游戏中显示的帧速率

    图 19.5:游戏中显示的帧速率

    如果不暂停游戏,就很难看到该值是多少。所以,当它降到某个值以下时,让我们让它改变颜色。这将使我们更容易看到我们担心的帧速率。我们将为此使用 fpsThreshold 值您在编辑器中运行的游戏可能以与我的不同的帧速率运行,因此请使用对您的系统有意义的值,以便能够看到代码的执行。我将使用1000 。在Inspector中输入您的fpsThreshould的值。

  16. Play your game, and you should see the fps displayed in the scene. If you open the Stats window, you should see that the values are relatively close.

    Figure 19.5: The frame rate displaying in-game

    It’s slightly difficult to see what the value is without pausing the game. So, let’s make it change color when it drops below a certain value. This will make it easier to see when there is a framerate we find worrisome. We’ll use the fpsThreshold value for this. Your game running in your Editor may run at a different framerate than mine, so please use a value that makes sense for your system to be able to see the code execute. I’m going to use 1000. Enter the value for your fpsThreshould in the Inspector.

  1. 现在,让我们让它在低于该阈值时改变颜色。在创建标签之前,将以下if / else语句添加到OnGUI()方法的顶部:
    如果 (fps < fpsThreshold) {
        GUI.内容颜色 = 颜色.红色;
    }
    别的 {
        GUI.内容颜色 = 颜色.白色;
    }
  2. Now, let’s make it change color when it goes below that threshold. Add the following if/else statement to the top of your OnGUI() method, before the label is created:
    if (fps < fpsThreshold) {
        GUI.contentColor = Color.red;
    }
    else {
        GUI.contentColor = Color.white;
    }

这就是您在游戏中需要显示帧速率估计值的全部内容。

And that’s all you need to have a framerate estimate display in your game.

显示游戏的帧速率是你可能想要通过游戏内调试实现的一个很好的例子UI。这样,即使您在编辑器之外运行游戏,也能轻松查看游戏的总体性能。您可以将其扩展为仅在执行特定任务时显示,或仅每秒更新一次。可能性无穷无尽。

Displaying a framerate for your game is a great example of something you might want to make with an in-game debug UI. This will let you easily see the general performance of your game, even when you run it outside of the Editor. You could extend this to only appear when you perform specific tasks or only update every second. The possibilities are endless.

您可能还想使用游戏内调试菜单,原因有很多。例如,您可能需要按钮来调用帮助您跳转到游戏各个部分的函数。或者,您可能需要一个按钮来清除已保存的数据。使用 IMGUI 制作游戏内 UI 时要记住的重要一点是,这一切都应该是为了帮助您(开发人员),而不应该用于向玩家显示信息。

There are lots of other reasons you may want to use an in-game debug menu. For example, you might want buttons that call functions that help you skip to sections of your game. Or maybe you want a button that clears your saved data. The important thing to remember about making in-game UI with IMGUI is this should all be to help you, the developer, but should not be used to display information to your player.

现在,让我们看一个您可以在编辑器中执行的操作来协助您的开发的示例。

Now, let’s look at an example of something you can do in your Editor to assist your development.

使用 IMGUI 制作导入数据的检查器按钮

Using IMGUI to make an Inspector button that imports data

在游戏开发中,你通常会将数据存储在某些外部源中,并需要将其导入为可用的格式您的游戏代码。例如,您的团队中可能有一个作家,他在 Excel 表中创建所有对话,然后您需要弄清楚如何将其导入到您的游戏中。此示例将展示一个基本示例,该示例使用 Inspector 按钮从文件中读取文本并将其分发到游戏中的适当位置,特别是ScriptableObject。

Often in game development, you will have data stored in some external source and need to import it into a usable format within your game’s code. For example, you may have a writer on your team who creates all dialogue in an Excel sheet, which you then need to figure out how to import into your game. This example will show a basic example that uses an Inspector button to read text from a file and distribute it to the appropriate place within your game, specifically a ScriptableObject.

图 19.6:带有导入按钮的自定义检查器

图 19.6:带有导入按钮的自定义检查器

Figure 19.6: A custom Inspector with an import button

笔记

Note

我们在本文中还没有真正讨论过 ScriptableObject。ScriptableObject 本质上是Unity 中的数据容器。

We haven’t really discussed ScriptableObjects in this text. A ScriptableObject is essentially a data container within Unity.

创建导入按钮数据放入你的ScriptableObject中,完成以下步骤:

To create a button that imports data into your ScriptableObject, complete the following steps:

  1. 创建一个名为DialogueData.cs的新 C# 脚本。
  2. Create a new C# script called DialogueData.cs.
  3. 在您的Assets文件夹中创建一个名为Data的新文件夹。
  4. Create a new folder in your Assets folder called Data.
  5. 从代码文件中,找到名为SampleDialogue.txt的文本文件。将其导入到您的Data文件夹中。或者,您可以创建一个至少包含三行文本的文本文件并将其放入此文件夹中。
  6. From the code files, find the text file called SampleDialogue.txt. Import it into your Data folder. Alternatively, you can create a text file with at least three lines of text and place it into this folder.
  7. 打开DialogueData.cs脚本并进行更改,使其从Monobehavior继承为ScriptableObject如下所示:
    公共类 DialogueData : ScriptableObject {
  8. Open your DialogueData.cs script and change it so that it inherits from Monobehavior to ScriptableObject, like so:
    public class DialogueData : ScriptableObject {
  9. 向类中添加以下两行代码:
    公共字符串文本路径;
    公共列表<string>导入的对话;

    我们将使用textPath变量来定义在项目中导入的文本文件的位置,并使用importedDialogue变量来保存所有导入的对话。

  10. Add the following two lines of code to the class:
    public string textPath;
    public List<string> importedDialogue;

    We’ll use the textPath variable to define where the text file being imported is within our project and the importedDialogue variable to hold all the imported dialogue.

  11. 添加以下行在类定义上方,创建一个菜单,使DialogueData成为ScriptableObjects:
    [CreateAssetMenu(fileName =“新 SO”,menuName =“DialogueData”,order = 1)]
  12. Add the following line above the class definition, to create a menu that makes DialogueData ScriptableObjects:
    [CreateAssetMenu(fileName = "New SO", menuName = "DialogueData", order = 1)]
  13. 现在,返回到编辑器并通过右键单击数据文件夹并选择创建| DialogueData来创建一个新的 ScriptableObject 。
    图 19.7:创建 DialogueData ScriptableObject

    图 19.7:创建 DialogueData ScriptableObject

    这将创建一个名为New SO的新 ScriptableObject,并具有以下 Inspector:

    图 19.8:ScriptableObject 的检查器

    图 19.8:ScriptableObject 的检查器

  14. Now, return to your Editor and create a new ScriptableObject by right-clicking within your Data folder and selecting Create | DialogueData.

    Figure 19.7: Creating the DialogueData ScriptableObject

    This will create a new ScriptableObject called New SO with the following Inspector:

    Figure 19.8: The ScriptableObject’s Inspector

  1. 现在,让我们用导入按钮自定义 Inspector。创建一个名为DialogueDataCustomEditor.cs的新脚本并将其保存在您的编辑器文件夹中。
  2. Now, let’s customize the Inspector with an import button. Create a new script called DialogueDataCustomEditor.cs and save it in your Editor folder.
  3. 打开脚本并使其继承Editor而不是Monobehavior中,如下所示:
    公共类DialogueDataCustomEditor:编辑器{
  4. Open the script and make it inherit from Editor instead of Monobehavior, like so:
    public class DialogueDataCustomEditor : Editor {
  5. 确保添加以下内容:
    使用 UnityEditor;
  6. Make sure to add the following:
    using UnityEditor;
  7. 现在,为了让脚本知道它是DialogueData类的自定义 Inspector,请在类定义上方添加以下内容:
    [自定义编辑器(类型(DialogueData))]
  8. Now, to let the script know that it is a custom Inspector for the DialogueData class, add the following above the class definition:
    [CustomEditor(typeof(DialogueData))]
  9. 将以下代码添加到脚本中以在Inspector 中显示一个按钮:
    公共覆盖无效OnInspectorGUI()
    {
        if (GUI.Button(new Rect(10, 10, 100, 50), "导入按钮"))
        {
            // 在此处理按钮点击逻辑
        }
    }

    如果你返回到你的新 SO,你会看到 Inspector 仅包含一个按钮。

  10. Add the following code to your script to display a button in the Inspector:
    public override void OnInspectorGUI()
    {
        if (GUI.Button(new Rect(10, 10, 100, 50), "Import Button"))
        {
            // Handle button click logic here
        }
    }

    If you return to your New SO, you’ll see that the Inspector only contains a button now.

图 19.9:检查器中的按钮

图 19.9:检查器中的按钮

Figure 19.9: The button in the Inspector

  1. 它不再显示我们想要在 Inspector 中显示的所有数据。因此,在按钮代码上方添加以下内容:
    绘制默认检查器();
  2. It no longer displays all of the data that we want to show in the Inspector. So, add the following, above your button code:
    DrawDefaultInspector();
  3. 现在应该绘制按钮位于默认的 Inspector 信息之上,这不是我们想要的。
    图 19.10:检查器中默认信息上的按钮

    图 19.10:检查器中默认信息上的按钮

    编辑按钮代码如下:

    if (GUILayout.Button("导入对话框")) {
    }

    这将导致按钮显示在组件的底部,利用 Unity GUI 的布局,而不是明确定位。

    图 19.11:使用 GUILayout 基类的按钮

    图 19.11:使用 GUILayout 基类的按钮

  4. It should now draw the button on top of the default Inspector information, which is not what we want.

    Figure 19.10: The button in the Inspector over the default information

    Edit the button code to the following:

    if (GUILayout.Button("Import Dialogue")) {
    }

    This will cause the button to display at the bottom of the component, utilizing the layout of the Unity GUI instead of being explicitly positioned.

    Figure 19.11: The button using the GUILayout base class

  1. 现在我们只需要导入对话。将以下变量声明添加到代码中:
    私有字符串[] splitTags = {“\r\n”,“\r”,“\n”};

    我们将使用这个变量来正确解析文本文件中的数据,确保每一行都是一行新的对话。

  2. Now, we just need to import the dialogue. Add the following variable declaration to your code:
    private string[] splitTags = { "\r\n", "\r", "\n"};

    We will use this variable to correctly parse the data in the text file, making sure each new line is a new line of dialogue.

  3. 现在,我们需要添加代码,首先从文件中获取数据,然后将其发送到ScriptableObject:
    私有无效ReadString(){
        对话数据对话数据脚本 = (对话数据)目标;
        StreamReader reader = new StreamReader(dialogueDataScript.textPath);
        ParseFile(reader.ReadToEnd());
        读者.关闭();
    }
    私有 void ParseFile(字符串 theFileText){
        调试.日志(文件文本);
        对话数据对话数据脚本 = (对话数据)目标;
        dialogDataScript.importedDialogue.Clear();
        字符串[] 行 = theFileText.Split(splitTags,StringSplitOptions.None);
        foreach (var line in lines) {
            dialogueDataScript.importedDialogue.添加(行);
            EditorUtility.SetDirty(目标);
        }
    }
  4. Now, we need to add code that will first get the data from the file and then send it to the ScriptableObject:
    private void ReadString() {
        DialogueData dialogueDataScript = (DialogueData)target;
        StreamReader reader = new StreamReader(dialogueDataScript.textPath);
        ParseFile(reader.ReadToEnd());
        reader.Close();
    }
    private void ParseFile(string theFileText) {
        Debug.Log(theFileText);
        DialogueData dialogueDataScript = (DialogueData)target;
        dialogueDataScript.importedDialogue.Clear();
        string[] lines = theFileText.Split(splitTags, StringSplitOptions.None);
        foreach (var line in lines) {
            dialogueDataScript.importedDialogue.Add(line);
            EditorUtility.SetDirty(target);
        }
    }
  5. 现在,更新您的OnInspectorGUI()方法调用ReadString()方法:
    公共覆盖无效OnInspectorGUI(){
            绘制默认检查器();
            if (GUILayout.Button("导入对话框")) {
                读取字符串();
            }
      }
  6. Now, update your OnInspectorGUI() method to call the ReadString() method:
    public override void OnInspectorGUI() {
            DrawDefaultInspector();
            if (GUILayout.Button("Import Dialogue")) {
                ReadString();
            }
      }
  7. 我们需要将SampleDialogue.txt文件的位置添加到我们的 ScriptableObject 中。右键单击SampleDialogue.txt并选择复制路径。将其粘贴到文本路径槽中。
  8. We need to add the location of the SampleDialogue.txt file to our ScriptableObject. Right-click on SampleDialogue.txt and select Copy Path. Paste it into the Text Path slot.
图 19.12:在 Inspector 中分​​配的 textPath 变量

图 19.12:在 Inspector 中分​​配的 textPath 变量

Figure 19.12: The textPath variable assigned in the Inspector

  1. 我们应该可以开始了。点击单击导入对话框按钮,对话框将立即导入。您的检查器应如图 19 .6所示。
  2. We should be good to go. Click on the Import Dialogue button and the dialogue will now import. Your Inspector should appear like Figure 19.6.

这就是使用 IMGUI 在编辑器中创建按钮的方法。

And that’s it for using IMGUI to create a button within the Editor.

概括

Summary

在本章中,我们讨论了如何使用 IMGUI 系统为开发人员、游戏内调试显示以及编辑器扩展构建 UI。虽然 IMGUI 不一定是推荐的 UI 系统,但它对于创建超快速工具来协助开发人员完成开发过程非常有用。

In this chapter, we discussed how to use the IMGUI system to build UI for both developers, in-game debug displays as well as Editor extensions. While IMGUI is not necessarily a recommended UI system, it is extremely helpful for creating super-quick tools to assist developers during the development process.

在下一章中,我们将了解 Unity 提供的另一个输入系统:新输入系统。

In the next chapter, we will look at the other input system provided by Unity: the New Input System.

20

20

新的输入系统

The New Input System

Unity 的输入系统(通俗地说称为新输入系统)允许您使有关游戏如何响应来自各种输入设备的交互的代码更加模块化。旧输入系统使用输入管理器来允许您定义可在代码中引用的各种轴(请参阅第 8 章)。您将在Update()方法中创建检查以确定设备是否收到输入。这通常会在您的Update()方法中产生多个 if-else 分支,用于控制收到的每个输入发生的情况

Unity’s Input System (colloquially referred to as the new Input System) allows you to make the code concerning how your game reacts to interactions from various input devices more modular. The old Input System used the Input Manager to allow you to define various Axes that could be referenced in code (see Chapter 8). You would create checks in your Update() method to determine if a device received an input. This usually results in multiple if-else branches within your Update() method that control what happens for each input received.

输入系统使用基于事件的编程方法,将单个输入设备的处理与代码分离开来。您无需在代码中创建对每个输入设备的引用,而是创建对特定操作作出反应的代码。然后,输入系统控制哪个输入设备的哪个交互触发这些操作。

The Input System divorces the handling of individual input devices from code by using an event-based programming methodology. Instead of having to create reference to each of your input devices within your code, you create code that reacts to specific actions. The Input System then controls which Interaction from which Input devices trigger these actions.

这种输入处理和代码分离允许您制作更多可自定义的控件,更轻松地处理不同的输入设备,以及更轻松地处理来自具有截然不同控制方案的不同控制台的控件。此外,输入系统的模块化允许您轻松地将控制方案复制到其他项目,因为所有信息都存储在资产中。

This separation of input handling and code allows you to make more customizable controls, more easily handle different Input Devices, and more easily handle controls from different consoles that have extremely different control schemes. Additionally, the modularity of the Input System allows you to easily copy your control schemes to other projects, since the information is all stored on an asset.

本章旨在介绍输入系统并概述关键概念,以便您可以在项目中开始使用输入系统。

This chapter is meant to be an introduction to the Input System and will give you an overview of the key concepts so that you can start using the Input System within your projects.

在本章中,我将讨论以下内容:

In this chapter, I will discuss the following:

  • 如何安装输入系统
  • How to install the Input System
  • 投票与订阅的区别
  • The differences in polling vs subscribing
  • 输入系统涉及的基本元素
  • The basic elements involved with the Input System
  • 如何编写使用输入系统控制游戏的代码
  • How to write code that uses the Input System’s to control your game
  • 如何将使用旧输入系统(输入管理器)的项目转换为使用新输入系统的项目
  • How to convert a project that uses the old Input System (the Input Manager) to one that uses the new Input System
  • 将输入系统连接到代码的两种不同方法
  • Two different ways to connect your Input System to your code

在我们开始了解如何使用输入系统之前,需要将其导入到您的项目中。让我们看看如何做到这一点。

Before we begin looking at how to work with the Input System, it needs to be imported into your project. Let’s look at how to do that.

技术要求

Technical requirements

您可以在此处找到本章的资产文件和代码: https: //github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2020

You can find the asset files and codes of this chapter here: https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2020

安装输入系统

Installing the Input System

输入系统是不再是预览版,而是正式成为 Unity 的一部分。但是,它不是预先打包在 Unity 中的,必须安装。您可以通过包管理器将输入系统安装到您的项目中,只需完成以下步骤即可。

The Input System is no longer in preview and is officially part of Unity. However, it does not come pre-packaged in unity and must be installed. You can install the Input System into your Project via the Package Manager, by completing the following steps.

  1. 从下拉菜单中选择Unity Registry
  2. Select Unity Registry from the dropdown menu.
图 20.1:在包管理器中更改包过滤器

图 20.1:在包管理器中更改包过滤器

Figure 20.1: Changing the Package filter in the Package Manager

  1. 在列表中搜索输入系统并选择安装
  2. Search for the Input System in the list and select Install.
图 20.2:在包管理器中查找输入系统

图 20.2:在包管理器中查找输入系统

Figure 20.2: Finding the Input System in the Package Manger

您将看到以下弹出窗口,您必须同意才能继续。

You will see the following pop up which you must agree to in order to proceed.

图 20.3:关于更改输入系统的警告

图 20.3:关于更改输入系统的警告

Figure 20.3: A Warning about changing input systems

警告是表示您使用旧输入系统编写的任何代码将不再起作用。这是一种破坏性操作,可能会破坏您的游戏。

The warning is indicating that any code you wrote using the old Input System will no longer function. This is a destructive action and may break your game.

笔记

Note

我建议您创建一个新项目来探索输入系统,因为安装它可能对您的项目造成破坏。它将导致使用输入管理器编写的所有代码停止运行。除非您觉得这样做很方便,否则请不要将其安装在现有项目中

I recommend you create a new project to explore the Input System as installing it may be destructive to your project. It will cause all code written using the Input Manager to cease functioning. Don’t install it in a pre-existing project until you are comfortable doing so.

重新打开项目后,您可能需要返回到包管理器并安装 Unity 随包提供的各种示例。我建议您安装简单演示和 UI vs Game Input。

Once you reopen your project, you may want to return to the Package Manager and install the various Samples provided by Unity with the Package. I recommend you install the Simple Demo and UI vs Game Input.

由于这是一个新的输入系统,因此它确实使用了一种比旧输入系统新的方法来访问输入。在开始研究之前,让我们先讨论一下这些方法之间的差异输入系统的各个元素

Since this is a new Input System, it does use a new methodology to access inputs than the old Input System. Let’s first discuss the differences in these methodologies before we begin looking at the various elements of the Input System.

投票与订阅

Polling vs subscribing

我们讨论过第 8 章中的输入管理器和直到最近,这是在 Unity 构建的游戏中跟踪设备输入的唯一方法。使用输入管理器时,您可以将特定的轴分配给不同的输入操作。然后,编写一个 C# 脚本,不断检查是否执行了该操作。

We discussed the Input Manager in Chapter 8 and up until recently, it was the only way to track inputs from devices in a Unity-built game. When using the Input Manager, you assign specific Axes to different input actions. You then write a C# script that constantly checks to see if that action is performed.

图 20.4:监视输入管理器定义的特定输入的 C# 脚本

图 20.4:监视输入管理器定义的特定输入的 C# 脚本

Figure 20.4: C# script watching for specific inputs defined by the Input Manager

为了实现这一点,您的代码看起来应该类似于以下伪代码:

To accomplish this, your code would look something like the following pseudocode:

无效更新(){
     如果(某些输入发生){
          做某事
     }
}
void Update () {
     if (some input happened){
          do something
     }
}

这种定期请求信息的技术称为轮询。您可以使用轮询模式从输入系统访问信息,方式与使用输入管理器的方式类似。但是,为了使您的代码更加模块化,使用输入管理器时,大多数代码系统将使用发布者-订阅者pub-sub)模式。

This technique of requesting information at a regular interval, is called polling. You can use the polling pattern for accessing information from the Input System in a similar way that you could with the Input Manager. However, to make your code more modular, most of your code when using the Input System will use a publisher-subscriber (pub-sub) pattern.

考虑以下类比:你非常喜欢某个 YouTube 内容创作者的内容。你每天都会查看他们的频道,看看他们是否发布了新内容。但是,你觉得这很麻烦,所以决定订阅他们的频道。现在,YouTube 频道会在发布新内容时提醒你,为你省去了不少工作。想象一下,如果你有数百个 YouTube 频道,想通过订阅他们的频道来了解最新动态,那么你可以省去多少工作量反而不断地检查他们的情况。

Consider the following analogy: you really enjoy content from a specific YouTube content creator. You check their channel every day to see if they have released new content. However, you find this cumbersome and instead decide to subscribe to their channel. Now the YouTube channel will alert you whenever new content is posted, saving you the work. Imagine how much work you save yourself from if you had hundreds of YouTube channels you wanted to be updated on by subscribing to their channels instead of constantly checking in on them.

让我们将这个类比与编码联系起来。您的 C# 脚本不必不断检查Update()方法中的各种输入,而是可以订阅由输入系统定义的特定事件。用更专业的术语来说,您将编写事件订阅者方法来监听由输入系统引发的特定事件。

Let’s tie the analogy to coding. Instead of your C# script having to constantly check in on the various inputs in the Update() method, you can instead subscribe to specific events defined by the Input System. In more technical speak, you will write event subscriber methods that listen for specific events raised by the Input System.

图 20.5:监听新输入系统定义的特定事件的 C# 脚本

图 20.5:监听新输入系统定义的特定事件的 C# 脚本

Figure 20.5: C# script listening for specific events defined by the New Input System

因此,当使用新输入系统时,您的代码可能看起来像以下伪代码:

So, when using the New Input System, your code may look something like the following pseudocode:

无效OnEnable(){
     订阅 DoSomething() 事件
}
私人做某事(){
     做某事
}
无效OnDisable(){
     取消订阅 DoSomething() 事件
}
void OnEnable() {
     subscribe DoSomething() to the event
}
private DoSomething() {
     do something
}
void OnDisable() {
     unsubscribe DoSomething() from the event
}

输入系统将根据您指定的输入确定调用哪些事件,并且您的代码将订阅这些事件,并在这些事件发生时执行适当的功能。

The Input System will determine which events to call based on inputs you specify and your code will subscribe to those events, performing the appropriate functions whenever those events happen.

那么,如何告诉输入系统根据哪种输入调用哪些事件?以及哪些类型可以从这些输入调用事件吗?通过使用操作、交互和输入绑定来实现这一点。现在让我们来探讨一下这些概念。

So, how do you tell the Input System which events to call based on which input? And what types of events can it call from those inputs? You do this through the use of Actions, Interactions, and Input Binding. Let’s explore these concepts now.

输入系统元素

Input System elements

在输入中在管理器中,您可以列出各种轴并定义哪些按钮可以触发这些轴。在输入系统中,您可以定义一组操作,并通过输入绑定描述输入设备的各种控件可以触发哪些交互这些操作

In the Input Manager, you list various Axes and define what buttons can trigger these axes. In the Input System, you define a set of Actions and describe what Interactions the various Controls of the Input Device can trigger those Actions through Input Bindings.

例如,您可以创建输入绑定,通过按下交互将键盘上的空格键绑定到跳跃动作,其中输入设备是键盘,控件是键盘上的所有键。

For example, you could create the Input Binding that binds the Space Bar on a keyboard to a jump Action through a pressed Interaction, where the Input device would be the keyboard and the Controls would be all the keys on the keyboard.

图 20.6:输入绑定的示例

图 20.6:输入绑定的示例

Figure 20.6: An example of an Input Binding

您可以创建这些行动集在已知的范围内作为动作图。动作图包含动作列表。动作包含有关输入绑定的信息。一般的想法是创建包含基于其目的的动作组的动作图,例如,所有角色控制动作都可以放在一个动作图中,而所有 UI 动作都可以放在单独的动作图中。

You can create these sets of Actions within what is known as an Action Map. Action Maps contain a list of Actions. The Actions contain the information about the Input Bindings. The general idea is to create Action Maps that contain groups of actions based on their purpose for example all character control Actions may go in one Action Map and all UI Actions may go in a separate one.

您可以在操作编辑器中查看操作地图、操作、绑定和交互。例如,以下是动作编辑器包含两个动作映射,分别称为播放器 (Player) UI (UI)

You can view your Action Maps, Actions, Bindings, and Interactions in the Action Editor. For example, here is the Action Editor with two Action Maps called Player and UI.

图 20.7:动作编辑器

图 20.7:动作编辑器

Figure 20.7: The Action Editor

玩家行动中地图上,您可以看到跳跃动作,以及空格[键盘]绑定和按压交互。

Within the Player Action Map, you can see the Jump Action, with the Space [Keyboard] Binding, and the Press Interaction.

要开始创建操作和操作映射,请在Assets文件夹中单击鼠标右键,然后选择“创建|输入操作” ,然后将新的输入操作资产命名为您想要的任何名称。我建议您将这些资产保存在 Assets文件夹中名为“输入”的文件夹中。执行此操作后,您应该得到如下所示的内容:

To start creating your Actions and Action Maps, right click within an Assets folder and select Create | Input Actions and name the new Input Action Asset whatever you wish. I recommend you save these assets in a folder called Inputs within your Assets folder. When you do so, you should get something that looks like the following:

图 20.8:动作资产的检查器和图标表示

图 20.8:动作资产的检查器和图标表示

Figure 20.8: An Action Asset’s Inspector and icon representation

您可以双击刚刚创建的资产,也可以从其检查器中选择“编辑资产”来查看动作编辑器。动作编辑器将包含动作图和动作列表,同时显示每个动作的属性。

You can either double click on the asset you just created or select Edit asset from its Inspector to view the Action Editor. The Action Editor will contain your list of Action Maps and Actions while displaying each of the Action’s properties.

图 20.9:新创建的动作资产的动作编辑器

图 20.9:新创建的动作资产的动作编辑器

Figure 20.9: The Action Editor of a newly created Action Asset

选择“动作地图”部分中的+号将创建一个新的动作地图。

Selecting the + sign in the Action Maps section will create a new Action Map.

新的动作地图将自动附带一个新动作,您可以对其进行重命名。

A new Action Map will automatically come with a New action, which you can rename.

图 20.10:带有新动作的动作地图

图 20.10:带有新动作的动作地图

Figure 20.10: An Action Map with its New action

选择<无绑定>将允许您创建输入绑定。

Selecting <No Binding> will allow you to create an Input Binding.

我将在本文末尾的示例部分介绍如何选择“绑定和交互”的示例章节,但是现在,让我们看看如何将这些操作挂接到您的代码上。

I will cover an example of how to select Binding and Interactions in the Examples section at the end of this chapter, but for now, let’s look at how you can hook these Actions to your code.

将操作连接到代码

Connecting Actions to code

有多个使用输入系统的代码中的操作。在本节中,我将概述最重要的主题,这些主题应该可以帮助您开始使用输入系统。

There are multiple ways to work with the Input System’s Actions in your code. In this section, I will give a general overview of the most important topics that should allow you to get started working with the Input System.

要将您的操作连接到您的代码,您需要使用以下语句导入输入系统

To connect your Actions to your code, you will need to import the InputSystem with the following statement:

使用 UnityEngine.InputSystem;
using UnityEngine.InputSystem;

我将讨论通过两种方式将操作与代码相连接:

There are two ways in which I will discuss connecting Actions to your Code:

  • 引用动作资产
  • Referencing the Action Asset
  • 使用PlayerInput组件
  • Using the PlayerInput component

笔记

Note

有关将操作连接到代码的其他方法的更多信息,请参阅以下Unity 文档:

For more information about alternate ways you can connect Actions to your code, see the following Unity documentation:

https://docs.unity3d.com/Packages/com.unity.inputsystem@1.7/manual/Workflows.xhtml

https://docs.unity3d.com/Packages/com.unity.inputsystem@1.7/manual/Workflows.xhtml

您可以引用 Action Asset 并创建InputActionsAsset 类型的变量,如下所示:

You can reference the Action Asset creating a variable of type InputActionsAsset like so:

[SerializeField] 私有 InputActionAsset 动作;
[SerializeField] private InputActionAsset actions;

然后您可以在检查器 (Inspector) 中分配操作的值

You can then assign the value of actions in the Inspector.

为了引用InputActionAsset中的特定 Action ,您可以创建一个InputAction类型的变量,如下所示:

And to reference the specific Action within a InputActionAsset, you could create a variable of type InputAction, like so:

私人输入动作玩家动作;
private InputAction playerAction;

然后,您可以通过在动作资产中查找特定的动作映射和动作来分配它,如下所示:

You could then assign it by finding the specific Action Map and Action within the Action Asset, like so:

playerMoveAction = action.FindActionMap("玩家").FindAction("移动");
playerMoveAction = actions.FindActionMap("Player").FindAction("Move");

一旦你找到参考 Action Asset,您需要启用和禁用适当的动作地图。例如,如果您有一个名为Player 的动作地图,则可以执行以下操作:

Once you find the reference to the Action Asset, you will need to enable and disable the appropriate Action Maps. For Example, if you had an Action Map named Player, you could do the following:

私有无效OnEnable()
{
    动作.FindActionMap("玩家").Enable();
}
私有无效OnDisable()
{
    动作.FindActionMap(“玩家”).Disable();
}
private void OnEnable()
{
    actions.FindActionMap("Player").Enable();
}
private void OnDisable()
{
    actions.FindActionMap("Player").Disable();
}

获得对InputActionAsset的引用后,您可以让您的方法订阅 Action 的各种回调。每个 Action 都有以下可订阅的回调:

Once you have a reference to the InputActionAsset, you can have your methods subscribe to the various callbacks of the Action. Each Action has the following callbacks that you can subscribe to:

  • 已执行:动作的交互已完成
  • Performed: The Action’s Interaction is complete
  • 已开始:动作的交互已开始
  • Started: The Action’s Interaction has started
  • 等待:动作已启用,正在等待输入以触发交互
  • Waiting: The Action is enabled and waiting for an Input to cause trigger an Interaction
  • 已取消:该动作的交互已被取消
  • Canceled: The Action’s Interaction has been canceled
  • 已禁用:由于操作已被禁用,因此无法接收任何输入
  • Disabled: The Action cannot receive any input as it is disabled

举例来说,您可以订阅名为Player 的动作地图上名为Jump 的动作,该动作由按下按钮触发,如下所示

So, for example, you could subscribe to an Action named Jump on an Action Map named Player that is triggered by a button press with something like the following:

action.FindActionMap("玩家").FindAction("跳跃").performed += OnJump;
actions.FindActionMap("Player").FindAction("Jump").performed += OnJump;

如果你想投票行动,而不是订阅回调事件,可以使用ReadValue<TValue>()方法,如下所示:

If you wish to poll the Actions rather than subscribe to the callback events, you use the ReadValue<TValue>() method like so:

Vector2 moveVector = playerMoveAction.ReadValue<Vector2>();
Vector2 moveVector = playerMoveAction.ReadValue<Vector2>();

如果您不太喜欢编写代码,您可以使用PlayerInput组件。PlayerInput组件允许指定各种操作调用 C# 脚本中的哪些方法

If you’re not a big fan of writing code, you can instead use the PlayerInput component. The PlayerInput component will allow you to specify which methods in your C# scripts are called by the various Actions.

图 20.11:默认的玩家输入组件

图 20.11:默认的玩家输入组件

Figure 20.11: The default Player Input component

使用此方法还是直接引用代码中的操作主要取决于个人喜好。它需要的代码较少,但需要更多的 Inspector 工作。作为一名职业程序员,我个人更喜欢在代码中引用操作的方法。我发现当有多个对象使用操作时,它更容易调试、更可自定义且编辑速度更快。但是,当我正在处理一个项目时,设计师在 Inspector 中进行更改,而他们不想在代码中工作,使用PlayerInput组件是一个很好的解决方案,因为它允许他们在没有我的情况下完成工作。您也可以使用组合,这完全取决于您的需求和偏好。

Using this method vs directly referencing the Actions in your code is mostly up to preference. It takes less code, but it takes more Inspector work. As a programmer by trade, I personally prefer the method that references the Actions in code. I find it easier to debug, more customizable, and quicker to edit when there are multiple objects using Actions. However, when I am working on a project that has designers making changes in the Inspector, who don’t want to work in code, using the PlayerInput component is a good solution as it allows them to do things without me. You can also use a combination and it’s all based on your needs and preferences.

笔记

Note

要了解有关使用代码访问操作的各种方法的更多信息,请参阅以下文档:

To learn more about the various ways you can access Actions with your code, see the following documentation:

https://docs.unity3d.com/Packages/com.unity.inputsystem@1.7/manual/Workflow-ActionsAsset.xhtml

https://docs.unity3d.com/Packages/com.unity.inputsystem@1.7/manual/Workflow-ActionsAsset.xhtml

现在我们有了关于 Unity 的总体思路输入系统如何工作,让我们看一些如何使用它的例子。

Now that we have a general idea of how the Unity Input System works, let’s look at some examples of how to work with it.

示例

Examples

现在我们已经回顾了输入系统入门基础知识,让我们看一些如何实现它的示例。我们将看一个非常基本的字符控制器示例。我们将从一个使用旧输入管理器的示例开始,然后调整它以使用输入系统

Now that we’ve reviewed the basics of getting started with the Input System, let’s look at some examples of how to implement it. We’ll look at one very basic example of a character controller. We’ll start with an example that uses the old Input Manager and then adjust it to use the Input System.

笔记

Note

这是一个超基础的字符控制器。它被简化了,以便更容易理解将其转换为输入系统的过程。

This is a super basic character controller. It is simplified to make the process of converting it to the Input System easier to understand.

我们将使用的例子只是一只跳跃和移动的猫。

The example we will use is just a cat that jumps and moves around.

图 20.12:使用旧输入系统的字符控制器示例

图 20.12:使用旧输入系统的字符控制器示例

Figure 20.12: The character controller example using the old Input System

在开始这些示例之前,请完成以下步骤:

Before you begin these examples, complete the following steps:

  1. 创建一个新的 2D Unity 项目。
  2. Create a new 2D Unity Project.
  3. 使用Assets | Import Package | Custom Package导入本书源代码提供的第 20 章- 示例 1 - 启动包

    在你导入包后,播放场景第 20 章- 示例 1,了解猫是如何移动的。它没有什么特别花哨或令人印象深刻的,但猫会用空格键跳跃,用箭头键和A - D键来回移动。

  4. Import the Chapter 20 – Example 1 - Start Package provided by the book’s source code using Assets | Import Package | Custom Package.

    After you’ve imported the package, play the scene Chapter 20 – Example 1 to get a feel of how the cat moves around. It’s nothing fancy or particularly impressive, but the cat will jump with the space bar and move back and forth with the arrow keys and A- D keys.

  5. 打开InputManagerBasicCharacterController.cs类并查看代码。代码的关键部分是Update()方法:
    无效更新()
    {
        运动 = Input.GetAxis("水平");
        catRigidbody.velocity = new Vector2(速度 * 运动, catRigidbody.velocity.y);
        if (grounded && Input.GetButtonDown("Jump"))
        {
            catRigidbody.AddForce(new Vector2(catRigidbody.velocity.x, jumpHeight));
        }
    }

    注意Input.GetAxis("Horizo​​ntal") Input.GetButtonDown ("Jump")的使用。这些是在输入管理器中定义的,您可以在编辑|项目设置|输入管理器中找到它。

  6. Open the InputManagerBasicCharacterController.cs class and review the code. The key section of code is the Update() method:
    void Update()
    {
        movement = Input.GetAxis("Horizontal");
        catRigidbody.velocity = new Vector2(speed * movement, catRigidbody.velocity.y);
        if (grounded && Input.GetButtonDown("Jump"))
        {
            catRigidbody.AddForce(new Vector2(catRigidbody.velocity.x, jumpHeight));
        }
    }

    Notice the use of Input.GetAxis("Horizontal") and Input.GetButtonDown ("Jump") . These are defined within the Input Manager which you can find in Edit | Project Settings | Input Manager.

  7. 现在您已经玩过游戏了。安装输入系统包并在要求时重新启动。请参阅安装输入系统部分了解步骤。

    如果您现在尝试玩游戏,您将在控制台中看到以下错误。

    InvalidOperationException:您正在尝试使用 UnityEngine.Input 类读取输入,但是您已在播放器设置中将活动输入处理切换为输入系统包。
    InputManagerBasicCharacterController.Update()(位于 Assets/Scripts/InputManagerBasicCharacterController.cs:20)

    这是因为当我们安装输入系统时,项目停止接受使用UnityEngine.Input的输入。猫将不再响应我们的按键而移动。

  8. Now that you’ve played the game. Install the Input System Package and restart when requested. See the Installing the Input System section for steps.

    If you try to play the game now, you will see the following error in your console.

    InvalidOperationException: You are trying to read Input using the UnityEngine.Input class, but you have switched active Input handling to Input System package in Player Settings.
    InputManagerBasicCharacterController.Update () (at Assets/Scripts/InputManagerBasicCharacterController.cs:20)

    This is because when we installed the Input System, the project stopped accepting inputs that use UnityEngine.Input. The cat will no longer move in response to our key presses.

好的,现在我们启动并设置好我们的项目后,我们可以将该代码转换为输入系统可以使用的代码!让我们从设置操作开始。

Ok, now that we have our project started and set up, we can convert that code to something that can be used by the Input System! Let’s start by setting up our Actions.

创建基本角色控制器动作

Creating basic character controller Actions

开始之前调整我们的代码,我们首先必须设置我们的动作地图和动作。

Before we start adjusting our code, we first have to set up our Action Map and Actions.

要设置基本角色控制器操作,请完成以下步骤:

To set up your basic character controller Actions, complete the following steps:

  1. 在Assets文件夹中创建一个名为Inputs的新文件夹
  2. Create a new folder called Inputs within your Assets folder.
  3. 在文件夹中单击鼠标右键,然后选择“创建” | “输入操作”
  4. Right-click within the folder and select Create | Input Actions.
  5. 将新的动作资产重命名CatActions
  6. Rename the new Action Asset to CatActions.
  7. 双击CatActions.inputActions以打开动作编辑器。
  8. Double-click CatActions.inputActions to open the Action Editor.
  9. 选中“自动保存”框,这样您所做的任何更改都会自动保存。
  10. Check the Auto-Save box so that any changes you make will automatically save.
图 20.13:选择自动保存

图 20.13:选择自动保存

Figure 20.13: Selecting Auto-Save

  1. 现在我们需要添加一个动作图,它将保存我们角色的所有动作。通过选择加号创建一个新的动作图,然后命名动作地图播放器。您现在应该看到以下内容:
  2. Now we need to add an Action Map that will hold all the Actions for our character. Create a new Action Map by selecting the plus sign and then naming the Action Map Player. You should now see the following:
图 20.14:玩家行动图

图 20.14:玩家行动图

Figure 20.14: The Player Action Map

  1. 让我们通过将新动作重命名为Jump来创建Jump动作。您可以通过双击New action来执行此操作。请注意,动作类型设置为按钮。这意味着 Jump Action 将由类似按钮的输入触发。
  2. Let’s create the Jump action by renaming New action to Jump. You can do so by double-clicking on the words New action. Notice that the Action Type is set to Button. This means the Jump Action will be triggered by button-like inputs.
图 20.15:跳跃动作的属性

图 20.15:跳跃动作的属性

Figure 20.15: The Jump Action’s properties

  1. 单击跳跃动作旁边的箭头查看其所有绑定。您应该看到<无绑定>。
  2. Click on the arrow next to the Jump Action to view all of its bindings. You should see <No Binding>.
图 20.16:跳跃动作的绑定

图 20.16:跳跃动作的绑定

Figure 20.16: The Jump Action’s bindings

  1. 点击在<No Binding>上,选择Path属性旁边的下拉列表。在出现的文本字段中,输入Space,然后选择Space [Keyboard] 。这会将键盘上的空格键绑定到此操作。请注意,它会自动为命名绑定。
  2. Click on <No Binding> and select the dropdown next to the Path property. Within the text field that appears, type Space, then select Space [Keyboard]. This will bind the spacebar on the keyboard to this Action. Notice it automatically names the binding for you.
  3. 现在让我们告诉此绑定它可以接受什么交互。选择交互旁边的加号,然后从下拉列表中选择按下。您的玩家动作图现在应如下所示:
  4. Now let’s tell this binding what Interaction it can accept. Select the plus sign next to Interactions and select Press from the dropdown. Your Player Action Map should now look as follows:
图 20.17:空格键 [键盘] 绑定上的按压交互

图 20.17:空格键 [键盘] 绑定上的按压交互

Figure 20.17: The Press Interaction on the Space [Keyboard] binding

  1. 现在我们要添加将绑定到之前在输入管理器中由“水平”引用的键的操作。选择操作旁边的加号以创建一个新的操作。将其命名为移动
  2. Now we want to add the Action that will be bound to the keys that were previously referenced by "Horizontal" in the Input Manager. Select the plus sign next to Actions to create a new Action. Call it Move.
  3. 将其操作类型设置为,并将其控制类型设置 Vector2
  4. Set its Action Type to Value and its Control Type to Vector2.
  5. 现在我们需要添加按键绑定。选择Move Action旁边的加号,您会注意到,由于我们将Move设置为与Jump不同的 Action Type ,因此它具有不同的Binding Options。选择Add Up\Down\Left\Right Composite。您现在应该看到以下内容:
  6. Now we need to add the key bindings. Select the plus sign next to the Move Action and you’ll notice, since we set Move as a different Action Type than Jump, it has different Binding Options. Select Add Up\Down\Left\Right Composite. You should now see the following:
图 20.18: 2D向量绑定

图 20.18: 2D向量绑定

Figure 20.18: The 2D vector bindings

  1. 删除UpDown绑定以及标记为<No Binding> 的绑定,因为我们不需要它们。您可以通过右键单击它们并选择删除来执行此操作。
  2. Delete the Up and Down Bindings as well as the one marked <No Binding>, because we do not need them. You can do this by right-clicking on them and selecting Delete.
  3. 选择左:<无绑定>。在路径中开始输入左箭头以找到箭头 [键盘]
  4. Select Left: <No Binding>. Start typing Left Arrow in the Path to find Left Arrow [Keyboard].
  5. 选择右:<无绑定>。在路径中开始输入右箭头以找到右箭头 [键盘]。您现在应该具有以下操作和绑定。
    图 20.19:左侧和右侧绑定

    图 20.19:左侧和右侧绑定

    现在,我们已将箭头键绑定到移动动作。

  6. Select Right: <No Binding>. Start typing Right Arrow in the Path to find Right Arrow [Keyboard]. You should now have the following Actions and Bindings.

    Figure 20.19: The Left and Right Bindings

    We have now bound our arrow keys to the move action.

  1. 现在我们需要绑定AD键。右键单击左:左箭头 [键盘]右:右箭头 [键盘]绑定并选择复制 ,以复制它们。
  2. Now we need to bind the A and D keys. Duplicate the Left: Left Arrow [Keyboard] and Right: Right Arrow [Keyboard] bindings by right clicking them and selecting Duplicate.
  3. 选择重复的左:左箭头 [键盘]并开始在路径中输入键盘以找到A [键盘]
  4. Select the duplicate Left: Left Arrow [Keyboard] and start typing a keyboard into the Path to find A [Keyboard].
  5. 选择重复的Right: Right Arrow [Keyboard]并在路径中输入d keyboard以找到D [Keyboard]。你现在应该看到以下内容动作和绑定:
  6. Select the duplicate Right: Right Arrow [Keyboard] and start typing d keyboard into the Path to find D [Keyboard]. You should now see the following Actions and Bindings:
图 20.20:所有必要的操作和绑定

图 20.20:所有必要的操作和绑定

Figure 20.20: All the necessary Actions and Bindings

现在我们已经完成了操作的连接,我们可以开始在代码中使用它们了!我将向您展示两种方法要做到这一点:使用PlayerInput组件并引用我们脚本中的动作

Now that we’re done hooking up our Actions, we can start using them with our code! I’ll show you two ways to do this: with the PlayerInput Component and by referencing the Action in our script.

使用 PlayerInput 组件创建基本角色控制器

Creating a basic character controller with the PlayerInput Component

切换我们的代码使用Actions和PlayerInput组件,需要一些代码调整和一些Inspector工作。

Switching our code to use Actions and the PlayerInput component, requires a bit of code adjustment and some Inspector work.

要将操作与PlayerInput组件结合使用,请完成以下步骤:

To use Actions with the PlayerInput Component, complete the following steps:

  1. 为了保留前面的示例,我将复制该场景并将其命名为第 20 章- 示例 2;复制InputManagerBasicCharacterController.cs脚本;并将副本重命名为PlayerInputBasicCharacterController.cs。如果您只想在同一个场景中使用相同的脚本,则可以跳过此步骤。但是,如果您确实想这样做,请确保在类定义中也更改脚本的名称,并将脚本作为组件添加到新场景中的Cat
  2. To preserve the previous example, I am going to duplicate the scene and call it Chapter 20 – Example 2; duplicate the InputManagerBasicCharacterController.cs script; and rename the duplicate to PlayerInputBasicCharacterController.cs. If you’d rather just work in the same scene with the same script, you can skip this step. However, if you do want to do this, make sure to also change the name of the script in the class definition and add the script as a component to the Cat in your new scene.
  3. 让我们从调整脚本开始。我们将删除Update()方法中检查输入轴的代码,转而使用可以连接到 Inspector 的公共方法。注释掉Update()方法中的所有代码。不要删除它,因为我们稍后会将其中一些代码剪切并粘贴到其他方法中。
  4. Let’s start by adjusting the script. We’ll be removing the code within the Update() method that checks for the Input Axes and instead use public methods that can be hooked up in the Inspector. Comment out all the code within the Update() method. Don’t delete it, b/c we’ll cut and paste some of it to other methods later.
  5. 添加将以下语句添加到脚本的顶部:
    使用 UnityEngine.InputSystem;
  6. Add the following statement to the top of your script:
    using UnityEngine.InputSystem;
  7. 创建一个名为OnJump() 的新方法。该方法将在触发跳跃动作时调用。它应如下所示:
    公共 void OnJump(InputAction.CallbackContext 上下文){
    }
  8. Create a new method called OnJump(). This will be the method that is called when the Jump Action is triggered. It should look as follows:
    public void OnJump(InputAction.CallbackContext context) {
    }
  9. 我们希望我们的OnJump()方法的执行效果与我们在Update()方法中注释掉的以下语句类似
    if (grounded && Input.GetButtonDown("Jump"))
    {
        catRigidbody.AddForce(new Vector2(catRigidbody.velocity.x, jumpHeight));
    }

    将其剪切并粘贴到OnJump()方法中。

  10. We want our OnJump() method to perform similarly to the following statement we have commented out in our Update() method.
    if (grounded && Input.GetButtonDown("Jump"))
    {
        catRigidbody.AddForce(new Vector2(catRigidbody.velocity.x, jumpHeight));
    }

    Cut and paste it into the OnJump() method.

  11. if语句中删除&& Input.GetButtonDown("Jump")。您的OnJump()方法现在应如下所示:
    公共 void OnJump(InputAction.CallbackContext 上下文){
            如果(接地){
                catRigidbody.AddForce(new Vector2(catRigidbody.velocity.x, jumpHeight));
            }
    }
  12. Remove && Input.GetButtonDown("Jump") from the if statement. Your OnJump() method should now appear as follows:
    public void OnJump(InputAction.CallbackContext context) {
            if (grounded) {
                catRigidbody.AddForce(new Vector2(catRigidbody.velocity.x, jumpHeight));
            }
    }
  13. 现在让我们在 Inspector 中挂接此方法。从层次结构中选择您的Cat ,然后添加PlayerInput组件。您的 Cat 的 Inspector 应该具有 以下组件:
  14. Now let’s hook this method up in the Inspector. Select your Cat from the Hierarchy and add the PlayerInput Component. Your Cat’s Inspector should have the following components:
图 20.21:Cat 上的一些组件

图 20.21:Cat 上的一些组件

Figure 20.21: Some of the components on Cat

  1. 现在让我们将操作添加到Player Input组件。将CatActions从 Project 文件夹拖到Actions插槽中。您的组件现在应如下所示:
    图 20.22:将 CatActions 分配给 PlayerInput 组件

    图 20.22:将 CatActions 分配给 PlayerInput 组件

    注意它已经找到我们的玩家动作地图并将其添加为默认地图。如果我们有更多动作地图,下拉列表中还会有其他地图可供选择。

  2. Now let’s add our Actions to the Player Input component. Drag your CatActions from the Project folder into the Actions slot. Your component should now look as follows:

    Figure 20.22: Assigning CatActions to the PlayerInput component

    Notice it already found our Player Action Map and added it as the Default Map. If we had more Action Maps, there would be others in the dropdown we could choose from.

  1. “行为”下拉菜单中选择“调用 Unity 事件”。这将添加一个事件设置,可以展开该设置以显示玩家事件列表。展开玩家将显示我们在玩家地图中定义的两个操作以及其他一些有用的操作。
  2. From the Behavior dropdown, select Invoke Unity Events. This will add an Events setting that can be expanded to reveal a list of Player Events. Expanding Player will show the two Actions we have defined within the Player Map along with some other useful Actions.
图 20.23:各种玩家动作事件

图 20.23:各种玩家动作事件

Figure 20.23: The Various Player Action Events

  1. 我们可以挂钩通过选择Jump(CallbackContext)下的加号,将Cat拖入 Object 槽,然后选择PlayerInputBasicCharacterController | OnJump,将OnJump()方法添加到Jump事件中。您的Jump事件现在应如下所示:
    图 20.24: 玩家跳跃事件调用我们的 OnJump 方法

    图 20.24: 玩家跳跃事件调用我们的 OnJump 方法

    玩游戏,当你按下空格键时你应该会看到猫跳跃。

  2. We can hook up our OnJump() method to the Jump Event by selecting the plus sign under Jump(CallbackContext), dragging the Cat into the Object slot, and then selecting PlayerInputBasicCharacterController | OnJump. Your Jump Event should now appear as follows:

    Figure 20.24: The Player Jump Event calling our OnJump method

    Play the game and you should see the cat jump when you press the spacebar.

  1. 现在让我们连接移动动作。返回到您的代码并创建以下方法:
    公共 void OnMove(InputAction.CallbackContext context) {¶}
  2. Now let’s hook up the Move Action. Return to your code and create the following method:
    public void OnMove(InputAction.CallbackContext context) {¶}
  3. 将以下变量声明添加到您的代码中:
    私有 Vector2 moveVector = new Vector2();
  4. Add the following variable declaration to your code:
    private Vector2 moveVector = new Vector2();
  5. 当我们从输入轴获取运动变量,我们使用以下行:
    运动 = Input.GetAxis("水平");

    在前面的代码中,移动是一个浮点变量。水平轴返回一个浮点数,而移动操作发送的是Vector2值。将以下代码添加到OnMove()方法中:

    移动向量 = 上下文.读取值<Vector2>();

    每当调用OnMove()方法时,这将获取移动动作的值。

  6. When we got the movement variable from the Input Axis, we used the following line:
    movement = Input.GetAxis("Horizontal");

    In the preceeding code, movement was a float variable. While the Horizontal Axis returned a float, the Move Action sends Vector2 value. Add the following line to your OnMove() method:

    moveVector = context.ReadValue<Vector2>();

    This will get the value of the Move Action whenever the OnMove() method is called.

  7. 返回到Update()方法并取消注释剩余代码。您的Update()方法应如下所示:
    无效更新()
    {
        运动 = Input.GetAxis("水平");
        catRigidbody.velocity = new Vector2(速度 * 运动, catRigidbody.velocity.y);
    }
  8. Return to your Update() method and uncomment the remaining code. Your Update() method should appear as follows:
    void Update()
    {
        movement = Input.GetAxis("Horizontal");
        catRigidbody.velocity = new Vector2(speed * movement, catRigidbody.velocity.y);
    }
  9. 删除以下使用旧输入系统的行。
    运动 = 输入.GetAxis("水平");
  10. Delete the following line that uses the old Input System.
    Movement = Input.GetAxis("Horizontal");
  11. 编辑剩余的行以使用moveVector而不是移动浮点。
    catRigidbody.速度 = new Vector2(速度 * 移动矢量.x, catRigidbody.速度.y);
  12. Edit the remaining line to use the moveVector instead of the movement float.
    catRigidbody.velocity = new Vector2(speed * moveVector.x, catRigidbody.velocity.y);
  13. 删除运动浮点变量声明,因为我们不再需要它。
  14. Remove the movement float variable declaration since we no longer need it.
  15. 返回到 Inspector 并将OnMove()方法添加到Move事件,方法是选择Move(CallbackContext)下的加号,将Cat拖入对象槽,然后选择PlayerInputBasicCharacterController | OnMove。您的移动事件现在应如下所示:
  16. Return to your Inspector and add the OnMove() method to the Move Event by selecting the plus sign under Move(CallbackContext), dragging the Cat into the Object slot, and then selecting PlayerInputBasicCharacterController | OnMove. Your Move Event should now appear as follows:
图 20.25: 玩家移动事件调用我们的 OnMove 方法

图 20.25: 玩家移动事件调用我们的 OnMove 方法

Figure 20.25: The Player Move Event calling our OnMove method

玩游戏,你的猫现在应该能够移动和跳跃!

Play the game and your cat should now be able to move and jump!

就我个人而言,我并不喜欢使用PlayerInput组件。由于我是一名程序员,我更喜欢将大部分时间花在代码编辑器上,而不是 Unity 编辑器上。我发现调试检查器中挂接的代码很难调试。当我使用此功能时,我会尝试通过在代码中添加注释来说明哪些组件调用了哪些方法,以便将来的自己(或我的其他编程同事)的工作更轻松。这可以使调试和理解代码的工作方式变得更容易一些。使用像 Jetbrains Rider 这样的代码编辑器可以减轻一些压力,因为它会指示代码在检查器中挂接的位置,但它并不总是显示所有必要的信息,而且您不能指望您的同事也拥有Rider IDE。如果您有类似的偏好,我将在下一个示例中向您展示如何以以代码为中心的方式执行此操作。

Personally, I’m not a fan of using the PlayerInput component. Since I am a programmer by trade, I prefer to spend most of my time in my code editor rather than the Unity Editor. I find debugging code that is hooked up in the inspector difficult to debug. When I do use this functionality, I try to make life easier for my future self (or my other programming coworkers) by adding comments to the code that states what components call what methods. This can make debugging and understanding the way the code works a little easier. Using a code editor like Jetbrains Rider can relieve some of this stress, as it will indicate where code is hooked up in the Inspector, but it does not always show all necessary information and you can’t count on your coworkers also having the Rider IDE. If you have a similar preference, I will show you in the next example how to do this in a code-centric way.

通过引用代码中的 Actions 来创建基本的角色控制器

Creating a basic character controller by referencing Actions in your code

重做我们之前的示例,以便它引用代码中的操作,完成以下步骤:

To redo our previous example so that it references the Actions in code, complete the following steps:

  1. 为了保留前面的示例,我将复制该场景并将其命名为第 20 章- 示例 3;复制PlayerInputBasicCharacterController.cs脚本;并将副本重命名为ActionReferenceBasicCharacterController.cs。如果您只想在同一个场景中使用相同的脚本,则可以跳过此步骤。但是,如果您确实想这样做,请确保在类定义中也更改脚本的名称,并将脚本作为组件添加到新场景中的Cat
  2. To preserve the previous example, I am going to duplicate the scene and call it Chapter 20 – Example 3; duplicate the PlayerInputBasicCharacterController.cs script; and rename the duplicate to ActionReferenceBasicCharacterController.cs. If you’d rather just work in the same scene with the same script, you can skip this step. However, if you do want to do this, make sure to also change the name of the script in the class definition and add the script as a component to the Cat in your new scene.
  3. Cat的 Inspector 中删除PlayerInput组件。请注意,如果您现在尝试玩游戏,猫不会移动或跳跃,因为我们删除了将输入绑定到代码的组件。
  4. Delete the PlayerInput component from your Cat’s Inspector. Notice if you try to play the game now, the cat will not move or jump, since we removed the component that tied the inputs to our code.
  5. 打开ActionReferenceBasicCharacterController.cs脚本。
  6. Open the ActionReferenceBasicCharacterController.cs script.
  7. 注释掉OnMove()方法。我们不会通过 Inspector 中的事件调用该方法,因此我们不需要该方法。但是,我们将编写类似的代码,因此我暂时将其注释掉,以便稍后将其复制并粘贴到正确的位置。
  8. Comment out the OnMove() method. We will not be calling this via an Event in the Inspector, so we do not need the method. However, we will write code similar to it, so I’ll comment it out for now so I can copy and paste it into the correct place later.
  9. 我们需要做的第一件事是获取保存所有操作的 Action Asset 的引用。创建以下变量声明:
    [SerializeField] 私有 InputActionAsset 动作;
  10. The first thing we need to do is get a reference to the Action Asset that holds all of our Actions. Create the following variable declaration:
    [SerializeField] private InputActionAsset actions;
  11. 我们可以继续将CatActions资源拖入Actions插槽,并将其连接到 Inspector 中。
  12. We can go ahead and hook this up in the Inspector by dragging the CatActions asset into the Actions slot.
  13. 返回脚本。添加一个空的OnEnable()方法和OnDisable()方法到您的脚本。我们稍后会向其中添加一些代码。

    我更喜欢将OnEnable()方法放在Awake()方法之下,因为它在该方法之后执行,并将OnDisable()方法放在脚本的底部,因为它将在我们所有其他方法之后执行。

  14. Return to the script. Add an empty OnEnable() method and OnDisable() method to your script. We’ll add some code to them momentarily.

    I prefer to put the OnEnable() method right under the Awake() method since it executes after it and the OnDisable() method at the bottom of the script since it will execute after all of our other methods.

  15. 由于我们通过代码引用操作,因此我们需要启用和禁用操作映射。将以下加粗的代码行添加到OnEnable()OnDisable()方法中。
    私有无效OnEnable()
    {
        动作.FindActionMap("玩家").Enable();
    }
    私有无效OnDisable()
    {
        动作.FindActionMap(“玩家”).Disable();
    }
  16. Since we are referencing our Actions via code, we will need to enable and disable our Action Map. Add the following boldened lines of code to your OnEnable() and OnDisable() methods.
    private void OnEnable()
    {
        actions.FindActionMap("Player").Enable();
    }
    private void OnDisable()
    {
        actions.FindActionMap("Player").Disable();
    }
  17. 现在让我们连接跳跃动作。我们将通过订阅跳跃动作的执行事件来实现这一点。将以下代码添加到您的OnEnable()方法中。
    action.FindActionMap("玩家").FindAction("跳跃").performed += OnJump;

    这应该足以让我们已经编写的OnJump()方法与您的 Action Map 配合使用。玩游戏,现在您可以看到猫在您按下空格键时跳跃。

  18. Now let’s hook up our Jump Action. We will accomplish this by subscribing to the Jump Action’s performed event. Add the following code to your OnEnable() method.
    actions.FindActionMap("Player").FindAction("Jump").performed += OnJump;

    This should be enough to get the OnJump() method we already wrote working with your Action Map. Play the game and you can now see the cat jump when you press the space bar.

  19. 虽然对于本示例来说并非完全必要,但我更喜欢养成在OnDisable()中取消订阅我订阅的任何事件的习惯。养成这样做的习惯将使我在将来需要时免于出现问题。因此,将以下代码行添加到OneDisable()方法的顶部
    action.FindActionMap("玩家").FindAction("跳跃").performed -= OnJump;
  20. While not entirely necessary for this example, I prefer to make a habit of always unsubscribing from any events I subscribe to in OnDisable(). Making a habit of doing it will save me from problems in the future when it is necessary. So, add the following line of code to the top of your OneDisable() method:
    actions.FindActionMap("Player").FindAction("Jump").performed -= OnJump;
  21. 现在让我们连接我们的移动动作。为此,我们可以创建对代表移动动作的InputAction的引用,以便通过代码轻松访问它。添加将以下变量声明添加到代码顶部:
    私人输入动作 playerMoveAction;
  22. Now let’s hook up our Move Action. To do this, we can create a reference to the InputAction that represents the Move Action to make it easily accessible via code. Add the following variable declaration to the top of your code:
    private InputAction playerMoveAction;
  23. 现在让我们初始化它。将以下代码行添加到您的OnEnable()方法中。
    playerMoveAction = action.FindActionMap("玩家").FindAction("移动");
  24. Now let’s initialize it. Add the following line of code to your OnEnable() method.
    playerMoveAction = actions.FindActionMap("Player").FindAction("Move");
  25. 我们不会订阅 Move Action,而是在Update()方法中对其进行轮询。将以下代码从注释掉的OnMove()方法中剪切并粘贴到Update()方法的顶部。您现在可以删除OnMove()方法。
    移动向量 = 上下文.读取值<Vector2>();
  26. Instead of subscribing to the Move Action, we’ll poll it in our Update() method. Cut and paste the following code from within your commented out OnMove() method to the top of your Update() method. You can delete the OnMove() method now.
    moveVector = context.ReadValue<Vector2>();
  27. 您将在context变量上收到错误。它是OnMove()方法的一个参数,在Update()方法中不存在。我们不会读取context的值,而是读取playerMoveAction的值。将context替换为playerMoveAction,以便您的Update()方法如下所示:
    无效更新()
    {
        移动向量 = playerMoveAction.ReadValue<Vector2>();
        catRigidbody.速度 = new Vector2(速度 * 移动矢量.x, catRigidbody.速度.y);
    }

    这足以让我们的猫咪通过 Actions 移动,而无需PlayerInput组件。玩游戏,当你按下相应的键时,观察猫咪移动和跳跃。

  28. You will get an error on the context variable. It was a parameter for the OnMove() method and does not exist within the Update() method. Instead of reading the value of context, we will read the value of playerMoveAction. Replace context with playerMoveAction so that your Update() method appears as follows:
    void Update()
    {
        moveVector = playerMoveAction.ReadValue<Vector2>();
        catRigidbody.velocity = new Vector2(speed * moveVector.x, catRigidbody.velocity.y);
    }

    And that should be sufficient to get our cat moving with Actions and without the PlayerInput component. Play the game and watch the cat move around and jump when you press the appropriate keys.

我知道将我们的特定项目设置为使用输入系统而不是使用输入管理器需要做更多的工作,而且似乎不值得。对于这个小例子来说,这可能是真的。但是,如果我们想创建一个跨平台版本的项目,接受来自多种设备类型的输入,那么在与为每个可能的输入配置添加一行新代码相比,Action Map 的工作量要小得多我们想与之合作。

I know that setting our specific project up to use the Input System rather than using the Input Manager was a lot more work and may not seem worth it. And for this tiny example, that is probably true. However, if we wanted to create a cross platform version of this project that accepted inputs from multiple types of devices, adding new Bindings to an Action within an Action Map is significantly less work than adding in a new line of code for each possible input configuration we want to work with.

概括

Summary

在本章中,我们介绍了如何使用新输入系统为您的游戏收集输入。这使我们能够制作一个易于定制的输入系统,从长远来看,该系统可以使通过各种输入设备控制我们的游戏变得更加容易。设置可能需要一些工作,但从长远来看,它可以为您节省很多精力

In this chapter, we covered how to use the New Input System to collect input for your game. This allows us to make an easily customizable input system that can make controlling our game via various input devices significantly easier in the long run. It may take a bit if set work to set up, but it can save you a lot of effort in the long run.

好了,这本书到此结束了!我没有更多的用户界面知识可以传授给你了。

And so, we come to an end of the book! I have no more User Interface knowledge to impart to you.

指数

Index

由于此电子书版本没有固定页码,下面的页码仅基于本书的印刷版超链接供参考。

As this ebook edition doesn't have fixed pagination, the page numbers below are hyperlinked for reference only, based on the printed edition of this book.

符号

Symbols

2D 游戏背景图像

2D game background image

排名 99 - 103

placing 99-103

2D 世界空间状态指示器 478 - 481

2D World Space status indicators 478-481

3D悬浮生命值条 481 - 487

3D hovering health bars 481-487

一个

A

无障碍设计 40,45

accessibility design 40, 45

认知和 情感

cognitive and emotional 48

听力和 言语

hearing and speech 47

流动

mobility 47

参考链接 48

reference link 48

视觉 46,47

vision 46, 47

动作编辑器 584

Action Editor 584

行动地图 584

Action Map 584

操作

Actions

连接,至代码 586 - 589

connecting, to code 586-589

美国信息交换标准代码 (ASCII) 247

American Standard Code for Information Interchange (ASCII) 247

锚柄 80 - 83

Anchor Handles 80-83

Android 屏幕分辨率

Android screen resolutions

参考链接 19

reference link 19

动画文本

animated text

创建 259

creating 259

动画剪辑 394 - 397

Animation Clips 394-397

动画事件 398 , 399

Animation Events 398, 399

动画事件 398 , 399

Animation Events 398, 399

动画控制器 399 - 405

Animator Controller 399-405

动画参数。在脚本 407、408设置

Animation Parameters. setting in scripts 407, 408

动画 406、407

Animator layers 406, 407

过渡动画的动画师 405 , 406

Animator of Transition Animations 405, 406

行为 409 , 411

Behaviours 409, 411

动画师的参数

Animator’s Parameters

设置,代码 424 - 427

setting, with code 424-427

ASCII 码表

ASCII Codes Table

参考链接 247

reference link 247

增强现实 (AR) 31 - 33

augmented reality (AR) 31-33

用于设计用户界面 (UI) 37

used, for designing user interface (UI) 37

自动布局组

automatic layout groups

网格布局组 116

Grid Layout Group 116

水平布局组 110

Horizontal Layout Group 110

类型 108 - 110

types 108-110

垂直布局组 115

Vertical Layout Group 115

B

背景画布预制件

Background Canvas prefab

创造 259 , 260

creating 259, 260

基本输入模块 162

BaseInputModule 162

基准测试 491

benchmarking 491

广告牌效果 477

billboard effect 477

按钮动画过渡

button Animation Transition

添加 224 - 229

adding 224-229

按钮控制 568

Button Control 568

按下按钮

Button presses

用于加载场景 222 - 224

used, for loading scenes 222-224

按钮

buttons

显式导航,选择 217 - 221

explicit navigation, selecting 217-221

首先选择,使用 210

First Selected, using 210

布局 211 - 217

laying out 211-217

浏览 209 , 210

navigating through 209, 210

尺码 25 - 27

sizes 25-27

C

C#

C#

UI,可与513交互

UI, making interactable with 513

画布组件 64

Canvas component 64

屏幕空间 -相机65,66

Screen Space-Camera 65, 66

屏幕空间-叠加 64 , 65

Screen Space-Overlay 64, 65

文本,缩放 474 - 477

text, scaling in 474-477

世界空间 66 , 67

World Space 66, 67

画布组组件 83 , 84

Canvas Group component 83, 84

Canvas Renderer 组件 74

Canvas Renderer component 74

Canvas Scalar 组件 68

Canvas Scalar component 68

恒定物理尺寸 71

Constant Physical Size 71

恒定像素大小 68 , 69

Constant Pixel Size 68, 69

根据屏幕尺寸缩放 69 , 70

Scale With Screen Size 69, 70

世界 72

World 72

插入符号 375

caret 375

C# 代码

C# code

使用,利用网络数据制作 VisualElement 和 Label 属性 554 - 563

using, to make VisualElement and Label properties with web data 554-563

中央处理器 (CPU) 490

Central Processing Unit (CPU) 490

字符间距

character spacing

调整 279 - 281

adjusting 279-281

圆形进度条

circular progress bar

创造 299 - 302

creating 299-302

复杂的战利品箱

complex loot box

动画 428 , 429

animating 428, 429

动画,设置 429 - 436

animations, setting up 429-436

动画,时间 436 - 455

animations, timing 436-455

状态机,建筑 436 , 437

State Machine, building 436, 437

控制 567

Controls 567

协程 532

coroutines 532

自定义字体 250 - 252 , 273 - 279

custom font 250-252, 273-279

字符间距,调整 279 - 281

character spacing, adjusting 279-281

字体大小,修改 279 - 281

font size, modifying 279-281

D

大字体

DaFont

URL 245

URL 245

为不便而设计的概念 44

designing for inconvenience concept 44

设备模拟器 19 - 21

Device Simulator 19-21

设备特定资源 29

device-specific resources 29

对话

dialogue

翻译 267 - 273

translating 267-273

叙事界面 5

diegetic interface 5

绘制调用 491

draw call 491

下拉组件 362 , 363

Dropdown component 362, 363

标题属性 364

caption properties 364

值改变时 (Int32) 365

On Value Changed (Int32) 365

选项属性 364

option properties 364

模板属性 364

template properties 364

下拉式菜单

dropdown menu

创建,使用图像 380

creating, with images 380

信息,使用下拉选择 388 - 390

information, using from dropdown selection 388-390

布局,带标题和项目图像 380 - 383

laying out, with caption and item images 380-383

下拉菜单模板 360 - 362

Dropdown Template 360-362

E

八向虚拟模拟摇杆

eight-directional virtual analog stick

创造 316 - 321

creating 316-321

使其浮动 321 - 325

making to float 321-325

事件输入 168 - 170

event inputs 168-170

事件系统 149 - 153

Event System 149-153

事件系统管理器 153

Event System Manager 153

阻力阈值属性 154

Drag Threshold property 154

首选房产 153

First Selected property 153

发送导航事件属性 154

Send Navigation Events property 154

事件触发器组件 163

Event Trigger component 163

动作,添加到 事件166、167

action, adding to event 166, 167

事件类型 164

event types 164

事件类型 164

event types 164

拖放事件 165

drag and drop events 165

其他 活动

other events 165

指针事件 164

pointer events 164

选择事件 165

selection events 165

扩展现实 (XR) 31

extended reality (XR) 31

F

F

视场 (FOV) 34

field of view (FOV) 34

钳工 126

Fitters 126

长宽比 适配组件127、128

Aspect Ratio Fitter component 127, 128

内容大小适配组件 126 , 127

Content Size Fitter component 126, 127

菲茨定律 7

Fitts’ Law 7

字体资源创建器

Font Asset Creator

参考链接 253

reference link 253

字体资源

font assets

参考链接 252

reference link 252

字体颜色

font color

修改,带标记 255

modifying, with markup 255

字体

fonts

上升计算模式 248

Ascent Calculation Mode 248

角色属性 247

Character property 247

自定义字体 250 - 252

custom fonts 250-252

动态字体设置 248 , 249

dynamic font settings 248, 249

字体资源 253

font assets 253

字体大小设置 246

Font Size setting 246

字体样式,导入 250

font styles, importing 250

导入 245 , 246

importing 245, 246

渲染模式设置 247

Rendering Mode setting 247

与245合作

working with 245

字体大小

font size

修改 279 - 281

modifying 279-281

修改,带标记 255

modifying, with markup 255

字体松鼠

Font Squirrel

URL 245

URL 245

字体样式

font style

修改,带标记 254

modifying, with markup 254

帧速率 490

Frame Rate 490

每秒帧数 (fps) 490

frames per second (fps) 490

全屏点击 27 , 28

full screen taps 27, 28

G

游戏

game

暂停 176 , 177

pausing 176, 177

游戏宽高比 7,8

game aspect ratio 7, 8

更换 8 - 13

changing 8-13

游戏 分辨率7,8

game resolution 7, 8

更换 8 - 13

changing 8-13

单分辨率,建筑 14 , 15

single resolution, building 14, 15

游戏视图

Game view

移动分辨率, 设置18、19

mobile resolution, setting 18, 19

GetAxis() 函数 159

GetAxis() function 159

GetButton() 函数 158

GetButton() function 158

GetKey() 函数 159

GetKey() function 159

GetMouseButton() 函数 160

GetMouseButton() function 160

字形指标

Glyph metrics

参考链接 248

reference link 248

Google 字体

Google Fonts

URL 245

URL 245

图形用户界面 (GUI)

graphical user interface (GUI)

定义 3 , 4

defining 3, 4

图形 Raycaster 组件 72 , 73 , 170

Graphic Raycaster component 72, 73, 170

图形处理单元 (GPU) 490

Graphics Processing Unit (GPU) 490

电网库存

grid inventory

布局 138 - 148

laying out 138-148

网格布局组 116 , 117

Grid Layout Group 116, 117

细胞大小 117

Cell Size 117

约束属性 120 , 121

Constraint property 120, 121

起始轴属性 119 , 120

Start Axis property 119, 120

开始角落属性 118 , 120

Start Corner property 118, 120

H

平视显示器 (HUD) 4 , 33 , 64

heads-up-display (HUD) 4, 33, 64

布局,创建 85 - 130

layout, creating 85-130

水平健康栏

horizontal health bar

创造 296 - 299

creating 296-299

水平布局组 110 , 111

Horizontal Layout Group 110, 111

子对齐属性 112 , 113

Child Alignment property 112, 113

子强制扩展属性 114 , 115

Child Force Expand property 114, 115

控件子项 Size 属性 113

Control Child Size property 113

填充属性 111 , 112

Padding property 111, 112

逆序排列属性 113

Reverse Arrangement property 113

间距属性 112

Spacing property 112

使用 Child Scale 属性 115

Use Child Scale property 115

HUD 选择菜单

HUD selection menu

布局 130 - 138

laying out 130-138

I

图像类型属性​​,UI 图像

Image Type property, UI Image

已填充 291 , 292

Filled 291, 292

简单 289

Simple 289

切片 289 , 290

Sliced 289, 290

平铺 291

Tiled 291

立即模式图形用户界面 (IMGUI) 502

Immediate Mode Graphical User Interface (IMGUI) 502

控件 567 - 570

Controls 567-570

示例 571

examples 571

在检查器 571

in Inspector 571

概述 566

overview 566

使用,创建检查器按钮 573 - 578

using, to create Inspector button 573-578

使用,在游戏中显示帧 速率572、573

using, to show framerate in-game 572, 573

输入

input

对于加速度计和陀螺仪 162 , 163

for accelerometer and gyroscope 162, 163

用于多点触控 162

for multi-touch 162

输入字段组件 366

Input Field component 366

字符限制属性 367

Character Limit property 367

字符验证选项 373 - 375

Character Validation options 373-375

内容类型属性 367 , 368

Content Type property 367, 368

隐藏移动输入属性 367

Hide Mobile Input property 367

输入类型 369

Input Types 369

键盘类型 369 - 372

Keyboard Types 369-372

线型选项 369

Line Type option 369

值改变时(字符串)和结束时编辑(字符串) 375、376

On Value Changed (String) and On End Edit (String) 375, 376

占位符属性 367

Placeholder property 367

插入符号和选择属性 375

properties, of caret and selection 375

属性,输入的文本和屏幕键盘 367

properties, of entered text and onscreen keyboards 367

只读属性 367

Read Only property 367

TextMeshPro 377 , 378

TextMeshPro 377, 378

文本属性 367

Text property 367

输入函数

input functions

按钮和按键 158

for buttons and key presses 158

获取轴 159

GetAxis 159

获取按钮 158

GetButton 158

获取密钥 159

GetKey 159

获取鼠标按钮 160

GetMouseButton 160

输入管理器 54 , 154 - 157

Input Manager 54, 154-157

参考链接 157

reference link 157

使用 暂停面板174、176

using, with Pause Panel 174, 176

输入模块 160

input modules 160

基本输入模块 162

BaseInputModule 162

指针输入模块 162

PointerInputModule 162

独立输入模块 161

Standalone Input Module 161

输入系统 54 , 579

Input System 54, 579

操作与代码连接 586 - 589

Actions to code, connecting 586-589

要素 583 - 586

elements 583-586

示例 589 , 590

examples 589, 590

安装 580 , 581

installing 580, 581

投票与订阅 581 - 583

polling, versus subscribing 581-583

输入系统,示例

Input System, examples

基本角色控制器动作,创建 591 - 594

basic character controller Actions, creating 591-594

基本角色控制器,通过引用代码中的 Actions 创建 600 - 602

basic character controller, creating by referencing Actions in your code 600-602

基本角色控制器,使用 PlayerInput 组件创建 595 - 599

basic character controller, creating with PlayerInput Component 595-599

输入系统 53

Input System 53

和新的输入系统,在 5455之间选择

and new Input System, selecting between 54, 55

输入管理器 54

Input Manager 54

新输入系统 54

new Input System 54

可交互的用户界面

interactable UI

考虑 35,36

consideration 35, 36

位置 35 , 36

placement 35, 36

接口 168

interface 168

类型 4 , 5

types 4, 5

库存物品

inventory items

拖放 178 - 188

dragging and dropping 178-188

库存面板

Inventory Panel

KeyCode,与172 - 174一起使用

KeyCode, using with 172-174

隐形 按钮区域207、208

invisible button zones 207, 208

K

键码

KeyCode

使用库存面板 172 - 174

using, with Inventory Panel 172-174

关键帧 226

keyframes 226

大号

L

布局元素 121 , 122

Layout Element 121, 122

弹性宽度和弹性高度属性 125

Flexible Width and Flexible Height properties 125

忽略布局属性 122 , 123

Ignore Layout property 122, 123

最小宽度和最小高度属性 123

Min Width and Min Height properties 123

首选宽度和首选高度属性 124

Preferred Width and Preferred Height properties 124

宽度和高度属性 123

Width and Height properties 123

M

MagicLeap

MagicLeap

参考链接 37

reference link 37

标记格式

markup format

探索 254

exploring 254

用于修改字体颜色和大小 255

used, for modifying font color and size 255

用于修改字体样式 254

used, for modifying font style 254

面具

masks

组件 328 , 329

component 328, 329

使用 328

using 328

元接口 5

meta interface 5

Milky Coffee 字体

Milky Coffee font

参考链接 261

reference link 261

混合现实(MR) 31,32

mixed reality (MR) 31, 32

参考链接 37

reference link 37

用于设计用户界面 (UI) 37

used, for designing user interface (UI) 37

移动宽高比

mobile aspect ratio

设备模拟器 19 - 21

Device Simulator 19-21

设置 18

setting 18

移动输入 29

mobile inputs 29

移动方向

mobile orientation

22-25号楼 ​

building 22-25

设置 18

setting 18

移动分辨率

mobile resolution

设置 18

setting 18

设置,在游戏视图中 18 , 19

setting, in Game view 18, 19

多点触控输入 162

multi-touch input 162

静音按钮

mute Buttons

精灵交换 302 - 308

with sprite swap 302-308

N

命名空间 150

namespace 150

新输入系统 54

new Input System 54

节点 400

nodes 400

非叙事类别 5

non-diegetic category 5

Noto 字体

Noto fonts

参考链接 247

reference link 247

O

对象池 496

object pooling 496

优化基础 490

optimization basics 490

CPU 491

CPU 491

帧速率 490

Frame Rate 490

图形处理器 490

GPU 490

P

平移和缩放

pan and zoom

使用鼠标和多点触控输入实现 188 - 194

implementing, with mouse and multi-touch input 188-194

面板设置 507

Panel Settings 507

粒子系统

Particle System

时间,播放战利品盒动画 467

timing, to play in loot box animation 467

粒子系统,显示在 UI 中

Particle System, that displays in UI

创造 459 - 466

creating 459-466

暂停面板

Pause Panel

输入管理器,使用 174 - 176

Input Manager, using with 174-176

物理 2D 射线投射器 170

Physics 2D Raycaster 170

枢轴点 80 - 83

Pivot Points 80-83

指针输入模块 162

PointerInputModule 162

投票

polling 582

与订阅 581 - 583

versus subscribing 581-583

弹出动画

pop in and out animation

设置 412 - 424

setting up 412- 424

弹出菜单

pop-up menus

隐藏,通过按键 171

hiding, with keypress 171

设置 103 - 106

setting up 103-106

显示,按键 171

showing, with keypress 171

弹出窗口

pop-up windows

动画,淡入淡出 411

animating, to fade in and out 411

预制 259

prefab 259

按住/长按功能

press-and-hold/long-press functionality

添加 308 - 312

adding 308-312

发布者-订阅者 (pub-sub) 模式 582

publisher-subscriber (pub-sub) pattern 582

R

R

射线投射器 170

raycasters 170

图形光线投射器 170

Graphic Raycaster 170

物理 2D 射线投射器 170

Physics 2D Raycaster 170

光线投射 170

raycasting 170

矩形遮罩 2D

Rect Mask 2D

组件 329 , 330

component 329, 330

矩形工具 76

Rect Tool 76

定位模式 76 , 77

positioning modes 76, 77

矩形变换组件 63 , 64 , 76 - 78

Rect Transform component 63, 64, 76-78

编辑模式 78 , 79

edit modes 78, 79

矩形工具 76

Rect Tool 76

重复按钮 569

RepeatButton 569

年代

S

场景

scene

创造 259 , 260

creating 259, 260

筛网 部分龙头27、28

screen portion taps 27, 28

滚动矩形组件 337 , 338

Scroll Rect component 337, 338

运动属性 338 , 339

movement properties 338, 339

值改变事件 341 , 342

On Value Changed event 341, 342

属性 339 - 341

properties 339-341

序列搜索者 27

Sequence Seekers 27

射击场风格的游戏

shooting gallery style game

参考链接 273

reference link 273

滑块组件 355 - 357

Slider component 355-357

值改变时(单次) 357 , 358

On Value Changed (Single) 357, 358

空间界面 5

spatial interface 5

独立输入模块 161

Standalone Input Module 161

状态机 400

state machine 400

静态四向虚拟方向键

static four-directional virtual D-Pad

创造 312 - 316

creating 312-316

统计窗口 491 , 492

statistics window 491, 492

样式表 507

Style Sheet 507

使用 256 , 257

using 256, 257

订阅

subscribing

与民意调查 581 - 583

versus polling 581-583

电视

T

文本

text

翻译 258 , 259

translating 258, 259

文本区域控件 569

TextArea Controls 569

文本框 文本

text box text

动画 262 - 267

animating 262-267

文本框窗口

text box windows

创建 260 , 261 , 262

creating 260, 261, 262

TextField 控件 569

TextField Control 569

文本网格Pro 281 , 358 , 359 , 377 , 378

TextMeshPro 281, 358, 359, 377, 378

控制设置 379

Control settings 379

输入字段设置 379

Input Field settings 379

选择时(字符串)和取消选择时(字符串) 379

On Select (String) and On Deselect (String) 379

参考链接 237

reference link 237

用于创建换行文本 282 - 286

used, for creating wrapped text 282-286

文本-​​TextMeshPro 236 , 238

Text-TextMeshPro 236, 238

额外设置 242 , 243

Extra Settings 242, 243

主要设置 239 - 242

Main Settings 239-242

文本输入属性 238 , 239

Text Input properties 238, 239

TextMeshPro 项目设置 243 , 245

TextMeshPro Project Settings 243, 245

纹理打包器

TexturePacker

参考链接 273

reference link 273

主题样式表 508

Theme Style Sheet 508

拇指区 28 , 29

thumb zone 28, 29

切换组件 350 , 351

Toggle component 350, 351

值改变时(布尔 事件352、353

On Value Changed (Boolean) events 352, 353

切换控制 570

Toggle Control 570

切换组组件 354

Toggle Group component 354

工具,用于确定绩效 491

tools, for determining performance 491

统计窗口 491 , 492

statistics window 491, 492

Unity 帧调试器 494

Unity Frame Debugger 494

Unity 分析器 492 - 494

Unity Profiler 492- 494

过渡属性,UI 按钮 200

Transition property, UI Button 200

动画过渡 204

Animation transition 204

色彩色调过渡 200 , 202

Color Tint transition 200, 202

200

None 200

精灵交换过渡类型 202 - 204

Sprite Swap transition type 202-204

U

UI 构建器

UI Builder

UI, 使用511、512创建

UI, creating with 511, 512

使用,布局菜单 538 - 554

using, to lay out menu 538-554

使用,进行动画转换 538 - 554

using, to make animation transitions 538-554

使用,制作编辑器窗口的 UI 518 - 527

using, to make Editor Window’s UI 518-527

使用,制作样式表 538 - 554

using, to make style sheets 538-554

UI 按钮 198 , 199

UI Button 198, 199

按钮组件 200

Button component 200

导航属性 205 - 207

Navigation property 205-207

过渡属性 200

Transition property 200

UI 画布 60 - 63

UI Canvas 60-63

画布组件 64

Canvas component 64

Canvas Renderer 组件 74

Canvas Renderer component 74

Canvas Scalar 组件 68

Canvas Scalar component 68

图形 Raycaster组件72、73

Graphic Raycaster component 72, 73

矩形变换组件 63 , 64

Rect Transform component 63, 64

UI 文档 507

UI Document 507

使用 513

using 513

UI 下拉菜单 358

UI Dropdown 358

創作 358 , 359

creating 358, 359

下拉组件 362 , 363

Dropdown component 362, 363

下拉菜单模板 359 - 362

Dropdown Template 359-362

UI 效果组件 292

UI effect components 292

轮廓组件 293 , 294

Outline component 293, 294

位置为 UV1 组件 294

Position As UV1 component 294

阴影组件 292 , 293

Shadow component 292, 293

UI 元素

UI elements

访问,代码 150

accessing, in code 150

布局 6

laying out 6

UI 变量类型 150 , 151

UI variable types 150, 151

UnityEngine.UI 命名空间 150

UnityEngine.UI namespace 150

UIElements 命名空间514、515

UIElements namespaces 514, 515

UI 层次结构 509 - 511

UI Hierarchy 509-511

UI 图像组件属性 288 , 289

UI Image component properties 288, 289

图像类型属性 ​​ 289

Image Type property 289

用户界面图像 84 , 85

UI Images 84, 85

UI 输入字段 365

UI Input Field 365

创造 365

creating 365

输入字段组件 366

Input Field component 366

UI 面板 75 , 76

UI Panel 75, 76

UI 滚动条

UI Scrollbars

部件 331 , 332

component 331, 332

执行

implementing 330

UI 滚动条,组件

UI Scrollbars, component

值改变事件 332 - 334

On Value Changed event 332-334

UI 滚动视图

UI Scroll View

示例 342

examples 342

执行 334 - 337

implementing 334-337

从现有菜单制作 342 - 348

making, from pre-existing menu 342-348

滚动矩形组件 337 , 338

Scroll Rect component 337, 338

UI 滑块 355

UI Slider 355

创造 355

creating 355

滑块 组件355、356

Slider component 355, 356

UI 系统 51 , 52

UI systems 51, 52

图像用户界面 52

IMGUI 52

选择 53

selecting between 53

UI 工具包 53

UI Toolkit 53

Unity 用户界面 (uGUI) 52

Unity UI (uGUI) 52

用户界面文本 61 , 84 , 85

UI Text 61, 84, 85

UI 文本游戏对象 232 , 233

UI Text GameObject 232, 233

角色属性 233

Character property 233

颜色属性 235

Color property 235

材料特性 235

Material property 235

段落属性 233 - 235

Paragraph properties 233-235

Raycast Padding 属性 236

Raycast Padding properties 236

Raycast Target 属性 236

Raycast Target property 236

参考链接 243

reference link 243

文本属性 233

Text property 233

UI 切换 350

UI Toggle 350

切换组件 350

Toggle component 350

切换组组件 354

Toggle Group component 354

使用 350

using 350

UI 工具包 53

UI Toolkit 53

示例 517

examples 517

概述 502 , 503

overview 502, 503

使用,制作编辑器虚拟宠物 517 , 518

using, to make Editor virtual pet 517, 518

使用,制作带有样式表和动画过渡的菜单 537

using, to make menu with style sheets and animation transitions 537

UI 工具包

UI Toolkit package

安装 503 - 506

installing 503-506

UI工具包系统

UI Toolkit system

面板设置 507

Panel Settings 507

零件 506 , 509

parts 506, 509

样式表 507

Style Sheet 507

主题样式表 508 , 509

Theme Style Sheet 508, 509

UI 文档 507

UI Document 507

Unity 编辑器

Unity Editor

C# 代码,编写 528 - 536

C# code, writing 528-536

Unity 可扩展标记语言 (UXML) 507

Unity Extensible Markup Language (UXML) 507

Unity 帧调试器 494

Unity Frame Debugger 494

Unity 分析器 492 , 494

Unity Profiler 492, 494

Unity UI优化策略 495

Unity UI optimization strategies 495

布局组使用, 最小495、496

Layout Groups use, minimizing 495, 496

多个画布和画布层次结构,使用 495

multiple Canvases and Canvas Hierarchies, using 495

物品丢失 496

objects, missing 496

射线投射计算,减少 497

Raycast computations, reducing 497

时间对象池,启用/ 禁用496、497

time object pooling, enabling/disabling 496, 497

通用设计 39

universal design 39

通用设计原则 40

universal design principles 40

公平使用

equitable use 40

灵活性使用

flexibility use 41

体力劳动

low physical effort 44

可感知信息

perceptible information 43

简单直观的使用 42

simple and intuitive use 42

规模和空间 方法

size and space approach 45

尺寸和空间 利用

size and space use 45

容错

tolerance of error 43

用户界面 (UI) 31 , 53

user interface (UI) 31, 53

使用UI Builder 511、512创建

creating, with UI Builder 511, 512

定义 3 , 4

defining 3, 4

为 AR 37设计

designing, for AR 37

设计,适用于 MR 36

designing, for MR 36

设计, 针对VR 33、34

designing, for VR 33, 34

使用 C# 513实现交互

making interactable, with C# 513

粒子 457 , 458

particles 457, 458

参考,获取 UI 文档变量514、515

reference, getting to UI Documents variables 514, 515

UIElements 命名空间 514

UIElements namespaces 514

Visual Element 事件,管理 516

Visual Element events, managing 516

视觉元素属性,访问 516、517

Visual Element properties, accessing 516, 517

V

垂直布局组 115 , 116

Vertical Layout Group 115, 116

虚拟连续体

virtuality continuum

参考链接 33

reference link 33

虚拟现实 (VR) 31 , 32

virtual reality (VR) 31, 32

用于设计用户界面 ( UI ) 33、34

used, for designing user interface (UI) 33, 34

视觉元素 509 - 511

Visual Elements 509-511

可视化用户界面

visual UI

考虑 34 , 35

consideration 34, 35

位置 34 , 35

placement 34, 35

西

W

世界空间

World Space

工作考虑 477

working, considerations 477

世界空间用户界面

World Space UI

使用 469 - 473

using 469-473

packtpub.com

packtpub.com

订阅我们的在线数字图书馆,即可全面访问 7,000 多本书籍和视频,以及行业领先的工具,帮助您规划个人发展并推进职业发展。如需更多信息,请访问我们的网站。

Subscribe to our online digital library for full access to over 7,000 books and videos, as well as industry leading tools to help you plan your personal development and advance your career. For more information, please visit our website.

为何要订阅?

Why subscribe?

  • 利用来自 4,000 多名行业专业人士的实用电子书和视频,减少学习时间,将更多时间用于编码
  • Spend less time learning and more time coding with practical eBooks and Videos from over 4,000 industry professionals
  • 使用专门为你制定的技能计划来提高你的学习能力
  • Improve your learning with Skill Plans built especially for you
  • 每月免费获得一本电子书或视频
  • Get a free eBook or video every month
  • 全面可搜索,轻松获取重要信息
  • Fully searchable for easy access to vital information
  • 复制粘贴、打印和收藏内容
  • Copy and paste, print, and bookmark content

您是否知道 Packt 为每本已出版的书籍提供电子书版本,并提供 PDF 和 ePub 文件?您可以在packtpub.com升级到电子书版本,作为印刷书籍客户,您有权享受电子书折扣。请通过customercare@packtpub.com与我们联系以了解更多详情。

Did you know that Packt offers eBook versions of every book published, with PDF and ePub files available? You can upgrade to the eBook version at packtpub.com and as a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at customercare@packtpub.com for more details.

www.packtpub.com,您还可以阅读一系列免费技术文章,注册一系列免费新闻通讯,并获得 Packt 书籍和电子书的独家折扣和优惠。

At www.packtpub.com, you can also read a collection of free technical articles, sign up for a range of free newsletters, and receive exclusive discounts and offers on Packt books and eBooks.

您可能喜欢的其他书籍

Other Books You May Enjoy

如果你喜欢这本书,你可能会对Packt 的其他书籍感兴趣:

If you enjoyed this book, you may be interested in these other books by Packt:

Unity 2022 移动游戏开发

Unity 2022 Mobile Game Development

约翰· P·多兰

John P. Doran

国际标准书号:978-1-80461-372-6

ISBN: 978-1-80461-372-6

  • 为您的手机游戏设计响应式 UI
  • Design responsive UIs for your mobile games
  • 检测碰撞、接收用户输入并创建玩家动作
  • Detect collisions, receive user input, and create player movements
  • 使用移动设备输入创建有趣的游戏元素
  • Create interesting gameplay elements using mobile device input
  • 添加自定义图标和演示选项
  • Add custom icons and presentation options
  • 使用 Unity 的移动通知包保持玩家的参与度
  • Keep players engaged by using Unity s mobile notification package
  • 将社交媒体融入你的项目
  • Integrate social media into your projects
  • 为您的游戏添加增强现实功能,以吸引现实世界的关注
  • Add augmented reality features to your game for real-world appeal
  • 利用后期处理和粒子效果让你的游戏更加精彩
  • Make your games juicy with post-processing and particle effects

将 Unity 与 Blender 融为一体进行 3D游戏开发

Mind-Melding Unity and Blender for 3D Game Development

斯宾塞·格雷

Spencer Grey

国际标准书号:978-1-80107-155-0

ISBN: 978-1-80107-155-0

  • 使用 Blender 将您的想象力转化为 3D 场景、道具和角色
  • Transform your imagination into 3D scenery, props, and characters using Blender
  • 掌握 Blender 中的 UV 展开和纹理模型
  • Get to grips with UV unwrapping and texture models in Blender
  • 了解如何在 Blender 中装配和制作动画模型
  • Understand how to rig and animate models in Blender
  • 在 Unity 中为自上而下、FPS 和其他类型的游戏制作动画和脚本模型
  • Animate and script models in Unity for top-down, FPS, and other types of games
  • 了解如何将自定义资产从 Blender 传输到 Unity 并返回
  • Find out how you can roundtrip custom assets from Blender to Unity and back
  • 熟悉 Unity 中的 ProBuilder、Timeline 和 Cinemachine 的基础知识
  • Become familiar with the basics of ProBuilder, Timeline, and Cinemachine in Unity

Packt 正在寻找像您这样的作者

Packt is searching for authors like you

如果您有兴趣成为 Packt 的作者,请访问authors.packtpub.com并立即申请。我们与成千上万像您一样的开发人员和技术专业人士合作,帮助他们与全球技术社区分享他们的见解。您可以进行一般申请,申请我们正在招募作者的特定热门主题,或提交您自己的想法。

If you’re interested in becoming an author for Packt, please visit authors.packtpub.com and apply today. We have worked with thousands of developers and tech professionals, just like you, to help them share their insight with the global tech community. You can make a general application, apply for a specific hot topic that we are recruiting an author for, or submit your own idea.

分享你的想法

Share Your Thoughts

你好!

Hi!

我是《使用 Unity 掌握 UI 开发》一书的作者 Ashley Godbold 。我真心希望您喜欢阅读这本书,并发现它有助于提高您的生产力和效率。

I am Ashley Godbold, author of Mastering UI Development with Unity. I really hope you enjoyed reading this book and found it useful for increasing your productivity and efficiency.

如果您能在亚马逊上留下评论分享您对这本书的看法,这将对我(和其他潜在读者!)非常有帮助。

It would really help me (and other potential readers!) if you could leave a review on Amazon sharing your thoughts on this book.

请访问以下链接留下您的评论:

Go to the link below to leave your review:

https://packt.link/r/180323539X

https://packt.link/r/180323539X

您的评论将帮助我们了解本书中哪些地方做得好,以及未来版本中哪些地方可以改进,所以我们真的很感激。

Your review will help us to understand what’s worked well in this book, and what could be improved upon for future editions, so it really is appreciated.

最好的祝愿,

Best wishes,

Ashley Godbold博士

Dr. Ashley Godbold

下载本书的免费 PDF 副本

Download a free PDF copy of this book

感谢您购买本书!

Thanks for purchasing this book!

您是否喜欢在旅途中阅读但又无法随身携带纸质书籍?

Do you like to read on the go but are unable to carry your print books everywhere?

您购买的电子书是否与您选择的设备不兼容?

Is your e-book purchase not compatible with the device of your choice?

别担心!现在购买每本 Packt 书籍,您都可以免费获得该书的无 DRM 的 PDF 版本

Don’t worry!, Now with every Packt book, you get a DRM-free PDF version of that book at no cost.

随时随地、使用任何设备进行阅读。搜索、复制您喜爱的技术书籍中的代码,并将其直接粘贴到您的应用程序中。

Read anywhere, any place, on any device. Search, copy, and paste code from your favorite technical books directly into your application.

福利不止于此,您还可以每天在收件箱中独家获得折扣、新闻通讯和精彩的免费内容

The perks don’t stop there, you can get exclusive access to discounts, newsletters, and great free content in your inbox daily

按照以下简单步骤即可获得好处:

Follow these simple steps to get the benefits:

  1. 扫描二维码或访问以下链接:
  2. Scan the QR code or visit the following link:

https://packt.link/free-ebook/9781803235394

https://packt.link/free-ebook/9781803235394

  1. 提交您的购买证明。
  2. Submit your proof of purchase.
  3. 就这样!我们会将免费 PDF 和其他福利直接发送到您的电子邮箱。
  4. That’s it! We’ll send your free PDF and other benefits to your email directly.